#
# complex.rb -
# $Release Version: 0.5 $
# $Revision: 1.3 $
# $Date: 1998/07/08 10:05:28 $
# by Keiju ISHITSUKA(SHL Japan Inc.)
#
# ----
#
# complex.rb implements the Complex class for complex numbers. Additionally,
# some methods in other Numeric classes are redefined or added to allow greater
# interoperability with Complex numbers.
#
# Complex numbers can be created in the following manner:
# - Complex(a, b)
# - Complex.polar(radius, theta)
#
# Additionally, note the following:
# - Complex::I (the mathematical constant i)
# - Numeric#im (e.g. 5.im -> 0+5i)
#
# The following +Math+ module methods are redefined to handle Complex arguments.
# They will work as normal with non-Complex arguments.
# sqrt exp cos sin tan log log10
# cosh sinh tanh acos asin atan atan2 acosh asinh atanh
#
#
# Numeric is a built-in class on which Fixnum, Bignum, etc., are based. Here
# some methods are added so that all number types can be treated to some extent
# as Complex numbers.
#
class Numeric
#
# Returns a Complex number (0,self).
#
def im
Complex(0, self)
end
#
# The real part of a complex number, i.e. self.
#
def real
self
end
#
# The imaginary part of a complex number, i.e. 0.
#
def image
0
end
alias imag image
#
# See Complex#arg.
#
def arg
Math.atan2!(0, self)
end
alias angle arg
#
# See Complex#polar.
#
def polar
return abs, arg
end
#
# See Complex#conjugate (short answer: returns self).
#
def conjugate
self
end
alias conj conjugate
end
#
# Creates a Complex number. +a+ and +b+ should be Numeric. The result will be
# a+bi.
#
def Complex(a, b = 0)
if b == 0 and (a.kind_of?(Complex) or defined? Complex::Unify)
a
else
Complex.new( a.real-b.imag, a.imag+b.real )
end
end
#
# The complex number class. See complex.rb for an overview.
#
class Complex < Numeric
@RCS_ID='-$Id: complex.rb,v 1.3 1998/07/08 10:05:28 keiju Exp keiju $-'
undef step
undef div, divmod
undef floor, truncate, ceil, round
def Complex.generic?(other) # :nodoc:
other.kind_of?(Integer) or
other.kind_of?(Float) or
(defined?(Rational) and other.kind_of?(Rational))
end
#
# Creates a +Complex+ number in terms of +r+ (radius) and +theta+ (angle).
#
def Complex.polar(r, theta)
Complex(r*Math.cos(theta), r*Math.sin(theta))
end
#
# Creates a +Complex+ number a+bi.
#
def Complex.new!(a, b=0)
new(a,b)
end
def initialize(a, b)
raise TypeError, "non numeric 1st arg `#{a.inspect}'" if !a.kind_of? Numeric
raise TypeError, "`#{a.inspect}' for 1st arg" if a.kind_of? Complex
raise TypeError, "non numeric 2nd arg `#{b.inspect}'" if !b.kind_of? Numeric
raise TypeError, "`#{b.inspect}' for 2nd arg" if b.kind_of? Complex
@real = a
@image = b
end
#
# Addition with real or complex number.
#
def + (other)
if other.kind_of?(Complex)
re = @real + other.real
im = @image + other.image
Complex(re, im)
elsif Complex.generic?(other)
Complex(@real + other, @image)
else
x , y = other.coerce(self)
x + y
end
end
#
# Subtraction with real or complex number.
#
def - (other)
if other.kind_of?(Complex)
re = @real - other.real
im = @image - other.image
Complex(re, im)
elsif Complex.generic?(other)
Complex(@real - other, @image)
else
x , y = other.coerce(self)
x - y
end
end
#
# Multiplication with real or complex number.
#
def * (other)
if other.kind_of?(Complex)
re = @real*other.real - @image*other.image
im = @real*other.image + @image*other.real
Complex(re, im)
elsif Complex.generic?(other)
Complex(@real * other, @image * other)
else
x , y = other.coerce(self)
x * y
end
end
#
# Division by real or complex number.
#
def / (other)
if other.kind_of?(Complex)
self*other.conjugate/other.abs2
elsif Complex.generic?(other)
Complex(@real/other, @image/other)
else
x, y = other.coerce(self)
x/y
end
end
def quo(other)
Complex(@real.quo(1), @image.quo(1)) / other
end
#
# Raise this complex number to the given (real or complex) power.
#
def ** (other)
if other == 0
return Complex(1)
end
if other.kind_of?(Complex)
r, theta = polar
ore = other.real
oim = other.image
nr = Math.exp!(ore*Math.log!(r) - oim * theta)
ntheta = theta*ore + oim*Math.log!(r)
Complex.polar(nr, ntheta)
elsif other.kind_of?(Integer)
if other > 0
x = self
z = x
n = other - 1
while n != 0
while (div, mod = n.divmod(2)
mod == 0)
x = Complex(x.real*x.real - x.image*x.image, 2*x.real*x.image)
n = div
end
z *= x
n -= 1
end
z
else
if defined? Rational
(Rational(1) / self) ** -other
else
self ** Float(other)
end
end
elsif Complex.generic?(other)
r, theta = polar
Complex.polar(r**other, theta*other)
else
x, y = other.coerce(self)
x**y
end
end
#
# Remainder after division by a real or complex number.
#
def % (other)
if other.kind_of?(Complex)
Complex(@real % other.real, @image % other.image)
elsif Complex.generic?(other)
Complex(@real % other, @image % other)
else
x , y = other.coerce(self)
x % y
end
end
#--
# def divmod(other)
# if other.kind_of?(Complex)
# rdiv, rmod = @real.divmod(other.real)
# idiv, imod = @image.divmod(other.image)
# return Complex(rdiv, idiv), Complex(rmod, rmod)
# elsif Complex.generic?(other)
# Complex(@real.divmod(other), @image.divmod(other))
# else
# x , y = other.coerce(self)
# x.divmod(y)
# end
# end
#++
#
# Absolute value (aka modulus): distance from the zero point on the complex
# plane.
#
def abs
Math.hypot(@real, @image)
end
#
# Square of the absolute value.
#
def abs2
@real*@real + @image*@image
end
#
# Argument (angle from (1,0) on the complex plane).
#
def arg
Math.atan2!(@image, @real)
end
alias angle arg
#
# Returns the absolute value _and_ the argument.
#
def polar
return abs, arg
end
#
# Complex conjugate (z + z.conjugate = 2 * z.real).
#
def conjugate
Complex(@real, -@image)
end
alias conj conjugate
#
# Compares the absolute values of the two numbers.
#
def <=> (other)
self.abs <=> other.abs
end
#
# Test for numerical equality (a == a + 0i).
#
def == (other)
if other.kind_of?(Complex)
@real == other.real and @image == other.image
elsif Complex.generic?(other)
@real == other and @image == 0
else
other == self
end
end
#
# Attempts to coerce +other+ to a Complex number.
#
def coerce(other)
if Complex.generic?(other)
return Complex.new!(other), self
else
super
end
end
#
# FIXME
#
def denominator
@real.denominator.lcm(@image.denominator)
end
#
# FIXME
#
def numerator
cd = denominator
Complex(@real.numerator*(cd/@real.denominator),
@image.numerator*(cd/@image.denominator))
end
#
# Standard string representation of the complex number.
#
def to_s
if @real != 0
if defined?(Rational) and @image.kind_of?(Rational) and @image.denominator != 1
if @image >= 0
@real.to_s+"+("+@image.to_s+")i"
else
@real.to_s+"-("+(-@image).to_s+")i"
end
else
if @image >= 0
@real.to_s+"+"+@image.to_s+"i"
else
@real.to_s+"-"+(-@image).to_s+"i"
end
end
else
if defined?(Rational) and @image.kind_of?(Rational) and @image.denominator != 1
"("+@image.to_s+")i"
else
@image.to_s+"i"
end
end
end
#
# Returns a hash code for the complex number.
#
def hash
@real.hash ^ @image.hash
end
#
# Returns "Complex(real, image)".
#
def inspect
sprintf("Complex(%s, %s)", @real.inspect, @image.inspect)
end
#
# +I+ is the imaginary number. It exists at point (0,1) on the complex plane.
#
I = Complex(0,1)
# The real part of a complex number.
attr :real
# The imaginary part of a complex number.
attr :image
alias imag image
end
class Integer
unless defined?(1.numerator)
def numerator() self end
def denominator() 1 end
def gcd(other)
min = self.abs
max = other.abs
while min > 0
tmp = min
min = max % min
max = tmp
end
max
end
def lcm(other)
if self.zero? or other.zero?
0
else
(self.div(self.gcd(other)) * other).abs
end
end
end
end
module Math
alias sqrt! sqrt
alias exp! exp
alias log! log
alias log10! log10
alias cos! cos
alias sin! sin
alias tan! tan
alias cosh! cosh
alias sinh! sinh
alias tanh! tanh
alias acos! acos
alias asin! asin
alias atan! atan
alias atan2! atan2
alias acosh! acosh
alias asinh! asinh
alias atanh! atanh
# Redefined to handle a Complex argument.
def sqrt(z)
if Complex.generic?(z)
if z >= 0
sqrt!(z)
else
Complex(0,sqrt!(-z))
end
else
if z.image < 0
sqrt(z.conjugate).conjugate
else
r = z.abs
x = z.real
Complex( sqrt!((r+x)/2), sqrt!((r-x)/2) )
end
end
end
# Redefined to handle a Complex argument.
def exp(z)
if Complex.generic?(z)
exp!(z)
else
Complex(exp!(z.real) * cos!(z.image), exp!(z.real) * sin!(z.image))
end
end
# Redefined to handle a Complex argument.
def cos(z)
if Complex.generic?(z)
cos!(z)
else
Complex(cos!(z.real)*cosh!(z.image),
-sin!(z.real)*sinh!(z.image))
end
end
# Redefined to handle a Complex argument.
def sin(z)
if Complex.generic?(z)
sin!(z)
else
Complex(sin!(z.real)*cosh!(z.image),
cos!(z.real)*sinh!(z.image))
end
end
# Redefined to handle a Complex argument.
def tan(z)
if Complex.generic?(z)
tan!(z)
else
sin(z)/cos(z)
end
end
def sinh(z)
if Complex.generic?(z)
sinh!(z)
else
Complex( sinh!(z.real)*cos!(z.image), cosh!(z.real)*sin!(z.image) )
end
end
def cosh(z)
if Complex.generic?(z)
cosh!(z)
else
Complex( cosh!(z.real)*cos!(z.image), sinh!(z.real)*sin!(z.image) )
end
end
def tanh(z)
if Complex.generic?(z)
tanh!(z)
else
sinh(z)/cosh(z)
end
end
# Redefined to handle a Complex argument.
def log(z)
if Complex.generic?(z) and z >= 0
log!(z)
else
r, theta = z.polar
Complex(log!(r.abs), theta)
end
end
# Redefined to handle a Complex argument.
def log10(z)
if Complex.generic?(z)
log10!(z)
else
log(z)/log!(10)
end
end
def acos(z)
if Complex.generic?(z) and z >= -1 and z <= 1
acos!(z)
else
-1.0.im * log( z + 1.0.im * sqrt(1.0-z*z) )
end
end
def asin(z)
if Complex.generic?(z) and z >= -1 and z <= 1
asin!(z)
else
-1.0.im * log( 1.0.im * z + sqrt(1.0-z*z) )
end
end
def atan(z)
if Complex.generic?(z)
atan!(z)
else
1.0.im * log( (1.0.im+z) / (1.0.im-z) ) / 2.0
end
end
def atan2(y,x)
if Complex.generic?(y) and Complex.generic?(x)
atan2!(y,x)
else
-1.0.im * log( (x+1.0.im*y) / sqrt(x*x+y*y) )
end
end
def acosh(z)
if Complex.generic?(z) and z >= 1
acosh!(z)
else
log( z + sqrt(z*z-1.0) )
end
end
def asinh(z)
if Complex.generic?(z)
asinh!(z)
else
log( z + sqrt(1.0+z*z) )
end
end
def atanh(z)
if Complex.generic?(z) and z >= -1 and z <= 1
atanh!(z)
else
log( (1.0+z) / (1.0-z) ) / 2.0
end
end
module_function :sqrt!
module_function :sqrt
module_function :exp!
module_function :exp
module_function :log!
module_function :log
module_function :log10!
module_function :log10
module_function :cosh!
module_function :cosh
module_function :cos!
module_function :cos
module_function :sinh!
module_function :sinh
module_function :sin!
module_function :sin
module_function :tan!
module_function :tan
module_function :tanh!
module_function :tanh
module_function :acos!
module_function :acos
module_function :asin!
module_function :asin
module_function :atan!
module_function :atan
module_function :atan2!
module_function :atan2
module_function :acosh!
module_function :acosh
module_function :asinh!
module_function :asinh
module_function :atanh!
module_function :atanh
end
# Documentation comments:
# - source: original (researched from pickaxe)
# - a couple of fixme's
# - RDoc output for Bignum etc. is a bit short, with nothing but an
# (undocumented) alias. No big deal.