core/rational.rb
#
# rational.rb -
# $Release Version: 0.5 $
# $Revision: 1.7 $
# $Date: 1999/08/24 12:49:28 $
# by Keiju ISHITSUKA(SHL Japan Inc.)
#
class Rational < Numeric
attr_reader :numerator
attr_reader :denominator
def *(other)
case other
when Rational
num = @numerator * other.numerator
den = @denominator * other.denominator
Rational(num, den)
when Integer
Rational(@numerator * other, @denominator)
when Float
to_f * other
else
a, b = other.coerce(self)
a * b
end
end
def **(other)
if other.kind_of?(Rational) && other.denominator == 1
other = other.numerator
end
case other
when Fixnum
if other > 0
Rational(@numerator ** other, @denominator ** other)
elsif other < 0
raise ZeroDivisionError, "divided by 0" if self == 0
Rational(@denominator ** -other, @numerator ** -other)
elsif other == 0
Rational.new(1, 1)
end
when Bignum
if self == 0
if other < 0
raise ZeroDivisionError, "divided by 0"
elsif other > 0
Rational.new(0, 1)
end
elsif self == 1
Rational.new(1, 1)
elsif self == -1
Rational.new(other.even? ? 1 : -1, 1)
else
to_f ** other
end
when Float
to_f ** other
when Rational
if self == 0 && other < 0
raise ZeroDivisionError, "divided by 0"
end
to_f ** other
else
a, b = other.coerce(self)
a ** b
end
end
def +(other)
case other
when Rational
num = @numerator * other.denominator + @denominator * other.numerator
den = @denominator * other.denominator
Rational(num, den)
when Integer
Rational(@numerator + other * @denominator, @denominator)
when Float
to_f + other
else
a, b = other.coerce(self)
a + b
end
end
def -(other)
case other
when Rational
num = @numerator * other.denominator - @denominator * other.numerator
den = @denominator * other.denominator
Rational(num, den)
when Integer
Rational(@numerator - other * @denominator, @denominator)
when Float
to_f - other
else
a, b = other.coerce(self)
a - b
end
end
def /(other)
case other
when Rational
num = @numerator * other.denominator
den = @denominator * other.numerator
Rational(num, den)
when Integer
raise ZeroDivisionError, "divided by 0" if other == 0
Rational(@numerator, @denominator * other)
when Float
to_f / other
else
redo_coerced :/, other
end
end
alias_method :divide, :/
alias_method :quo, :/
def <=>(other)
case other
when Rational
diff = @numerator * other.denominator - @denominator * other.numerator
diff <=> 0
when Integer
diff = @numerator - @denominator * other
diff <=> 0
when Float
to_f <=> other
else
if defined?(other.coerce)
a, b = other.coerce(self)
a <=> b
end
end
end
def ==(other)
case other
when Rational
@numerator == other.numerator && @denominator == other.denominator
when Integer
@numerator == other && @denominator == 1
when Float
to_f == other
else
other == self
end
end
def abs
return self if @numerator >= 0
Rational.new(-@numerator, @denominator)
end
def ceil(precision = 0)
if precision == 0
-(-@numerator / @denominator)
else
with_precision(:ceil, precision)
end
end
def coerce(other)
case other
when Integer
return Rational.new(other, 1), self
when Float
return other, self.to_f
else
super
end
end
def floor(precision = 0)
if precision == 0
@numerator / @denominator
else
with_precision(:floor, precision)
end
end
def hash
@numerator.hash ^ @denominator.hash
end
def inspect
"(#{to_s})"
end
def rationalize(eps = undefined)
return self if undefined.equal?(eps)
e = eps.abs
a = self - e
b = self + e
p0 = 0
p1 = 1
q0 = 1
q1 = 0
while true
c = a.ceil
break if c < b
k = c - 1
p2 = k * p1 + p0
q2 = k * q1 + q0
t = 1 / (b - k)
b = 1 / (a - k)
a = t
p0 = p1
q0 = q1
p1 = p2
q1 = q2
end
# The rational number is guaranteed to be in lowest terms.
Rational.new(c * p1 + p0, c * q1 + q0)
end
def round(precision = 0)
return with_precision(:round, precision) unless precision == 0
return 0 if @numerator == 0
return @numerator if @denominator == 1
num = @numerator.abs * 2 + @denominator
den = @denominator * 2
approx = num / den
if @numerator < 0
-approx
else
approx
end
end
def to_f
@numerator.to_f / @denominator.to_f
end
def to_i
truncate
end
def to_r
self
end
def to_s
"#{@numerator.to_s}/#{@denominator.to_s}"
end
def truncate(precision = 0)
if precision == 0
@numerator < 0 ? ceil : floor
else
with_precision(:truncate, precision)
end
end
def self.convert(num, den, mathn = true)
if num.nil? || den.nil?
raise TypeError, "cannot convert nil into Rational"
end
if num.kind_of?(Integer) && den.kind_of?(Integer)
return reduce(num, den, mathn)
end
case num
when Integer
# nothing
when Float, String, Complex
num = num.to_r
end
case den
when Integer
# nothing
when Float, String, Complex
den = den.to_r
end
if den.equal?(1) && !num.kind_of?(Integer)
return Rubinius::Type.coerce_to(num, Rational, :to_r)
elsif num.kind_of?(Numeric) && den.kind_of?(Numeric) &&
!(num.kind_of?(Integer) && den.kind_of?(Integer))
return num / den
end
reduce(num, den)
end
def self.reduce(num, den, mathn = true)
case num
when Integer
# nothing
when Numeric
num = num.to_i
else
raise TypeError, "numerator is not an Integer"
end
case den
when Integer
if den == 0
raise ZeroDivisionError, "divided by 0"
elsif den < 0
num = -num
den = -den
end
if den == 1
return (mathn && Rubinius.mathn_loaded?) ? num : new(num, den)
end
when Numeric
den = den.to_i
else
raise TypeError, "denominator is not an Integer"
end
gcd = num.gcd(den)
num = num / gcd
den = den / gcd
return num if mathn && Rubinius.mathn_loaded? && den == 1
new(num, den)
end
class << self
private :convert
private :reduce
end
def initialize(num, den)
@numerator = num
@denominator = den
end
private :initialize
def marshal_dump
ary = [@numerator, @denominator]
instance_variables.each do |ivar|
ary.instance_variable_set(ivar, instance_variable_get(ivar))
end
ary
end
private :marshal_dump
def marshal_load(ary)
@numerator, @denominator = ary
ary.instance_variables.each do |ivar|
instance_variable_set(ivar, ary.instance_variable_get(ivar))
end
self
end
private :marshal_load
def with_precision(method, n)
raise TypeError, "not an Integer" unless n.kind_of?(Integer)
p = 10 ** n
s = self * p
r = Rational(s.send(method), p)
n < 1 ? r.to_i : r
end
private :with_precision
end