Tag unstable CGI specs.
[rbx.git] / lib / rational.rb
blobce754cfa3c14473aaf79246c9c88345b377f5a12
2 #   rational.rb -
3 #       $Release Version: 0.5 $
4 #       $Revision: 1.7 $
5 #       $Date: 1999/08/24 12:49:28 $
6 #       by Keiju ISHITSUKA(SHL Japan Inc.)
8 # Documentation by Kevin Jackson and Gavin Sinclair.
9
10 # When you <tt>require 'rational'</tt>, all interactions between numbers
11 # potentially return a rational result.  For example:
13 #   1.quo(2)              # -> 0.5
14 #   require 'rational'
15 #   1.quo(2)              # -> Rational(1,2)
16
17 # See Rational for full documentation.
22 # Creates a Rational number (i.e. a fraction).  +a+ and +b+ should be Integers:
23
24 #   Rational(1,3)           # -> 1/3
26 # Note: trying to construct a Rational with floating point or real values
27 # produces errors:
29 #   Rational(1.1, 2.3)      # -> NoMethodError
31 def Rational(a, b = 1)
32   if a.kind_of?(Rational) && b == 1
33     a
34   else
35     Rational.reduce(a, b)
36   end
37 end
40 # Rational implements a rational class for numbers.
42 # <em>A rational number is a number that can be expressed as a fraction p/q
43 # where p and q are integers and q != 0.  A rational number p/q is said to have
44 # numerator p and denominator q.  Numbers that are not rational are called
45 # irrational numbers.</em> (http://mathworld.wolfram.com/RationalNumber.html)
47 # To create a Rational Number:
48 #   Rational(a,b)             # -> a/b
49 #   Rational.new!(a,b)        # -> a/b
51 # Examples:
52 #   Rational(5,6)             # -> 5/6
53 #   Rational(5)               # -> 5/1
54
55 # Rational numbers are reduced to their lowest terms:
56 #   Rational(6,10)            # -> 3/5
58 # But not if you use the unusual method "new!":
59 #   Rational.new!(6,10)       # -> 6/10
61 # Division by zero is obviously not allowed:
62 #   Rational(3,0)             # -> ZeroDivisionError
64 class Rational < Numeric
65   @RCS_ID='-$Id: rational.rb,v 1.7 1999/08/24 12:49:28 keiju Exp keiju $-'
67   #
68   # Reduces the given numerator and denominator to their lowest terms.  Use
69   # Rational() instead.
70   #
71   def Rational.reduce(num, den = 1)
72     raise ZeroDivisionError, "denominator is zero" if den == 0
74     if den < 0
75       num = -num
76       den = -den
77     end
78     gcd = num.gcd(den)
79     num = num.div(gcd)
80     den = den.div(gcd)
81     if den == 1 && defined?(Unify)
82       num
83     else
84       new!(num, den)
85     end
86   end
88   #
89   # Implements the constructor.  This method does not reduce to lowest terms or
90   # check for division by zero.  Therefore #Rational() should be preferred in
91   # normal use.
92   #
93   def Rational.new!(num, den = 1)
94     new(num, den)
95   end
97   private_class_method :new
99   #
100   # This method is actually private.
101   #
102   def initialize(num, den)
103     if den < 0
104       num = -num
105       den = -den
106     end
107     if num.kind_of?(Integer) and den.kind_of?(Integer)
108       @numerator = num
109       @denominator = den
110     else
111       @numerator = num.to_i
112       @denominator = den.to_i
113     end
114   end
116   #
117   # Returns the addition of this value and +a+.
118   #
119   # Examples:
120   #   r = Rational(3,4)      # -> Rational(3,4)
121   #   r + 1                  # -> Rational(7,4)
122   #   r + 0.5                # -> 1.25
123   #
124   def + (a)
125     if a.kind_of?(Rational)
126       num = @numerator * a.denominator
127       num_a = a.numerator * @denominator
128       Rational(num + num_a, @denominator * a.denominator)
129     elsif a.kind_of?(Integer)
130       self + Rational.new!(a, 1)
131     elsif a.kind_of?(Float)
132       Float(self) + a
133     else
134       x, y = a.coerce(self)
135       x + y
136     end
137   end
139   #
140   # Returns the difference of this value and +a+.
141   # subtracted.
142   #
143   # Examples:
144   #   r = Rational(3,4)    # -> Rational(3,4)
145   #   r - 1                # -> Rational(-1,4)
146   #   r - 0.5              # -> 0.25
147   #
148   def - (a)
149     if a.kind_of?(Rational)
150       num = @numerator * a.denominator
151       num_a = a.numerator * @denominator
152       Rational(num - num_a, @denominator*a.denominator)
153     elsif a.kind_of?(Integer)
154       self - Rational.new!(a, 1)
155     elsif a.kind_of?(Float)
156       Float(self) - a
157     else
158       x, y = a.coerce(self)
159       x - y
160     end
161   end
163   #
164   # Returns the product of this value and +a+.
165   #
166   # Examples:
167   #   r = Rational(3,4)    # -> Rational(3,4)
168   #   r * 2                # -> Rational(3,2)
169   #   r * 4                # -> Rational(3,1)
170   #   r * 0.5              # -> 0.375
171   #   r * Rational(1,2)    # -> Rational(3,8)
172   #
173   def * (a)
174     if a.kind_of?(Rational)
175       num = @numerator * a.numerator
176       den = @denominator * a.denominator
177       Rational(num, den)
178     elsif a.kind_of?(Integer)
179       self * Rational.new!(a, 1)
180     elsif a.kind_of?(Float)
181       Float(self) * a
182     else
183       x, y = a.coerce(self)
184       x * y
185     end
186   end
188   #
189   # Returns the quotient of this value and +a+.
190   #   r = Rational(3,4)    # -> Rational(3,4)
191   #   r / 2                # -> Rational(3,8)
192   #   r / 2.0              # -> 0.375
193   #   r / Rational(1,2)    # -> Rational(3,2)
194   #
195   def / (a)
196     if a.kind_of?(Rational)
197       num = @numerator * a.denominator
198       den = @denominator * a.numerator
199       Rational(num, den)
200     elsif a.kind_of?(Integer)
201       raise ZeroDivisionError, "division by zero" if a == 0
202       self / Rational.new!(a, 1)
203     elsif a.kind_of?(Float)
204       Float(self) / a
205     else
206       x, y = a.coerce(self)
207       x / y
208     end
209   end
211   #
212   # Returns this value raised to the given power.
213   #
214   # Examples:
215   #   r = Rational(3,4)    # -> Rational(3,4)
216   #   r ** 2               # -> Rational(9,16)
217   #   r ** 2.0             # -> 0.5625
218   #   r ** Rational(1,2)   # -> 0.866025403784439
219   #
220   def ** (other)
221     if other.kind_of?(Rational)
222       Float(self) ** other
223     elsif other.kind_of?(Integer)
224       if other > 0
225         num = @numerator ** other
226         den = @denominator ** other
227       elsif other < 0
228         num = @denominator ** -other
229         den = @numerator ** -other
230       elsif other == 0
231         num = 1
232         den = 1
233       end
234       Rational.new!(num, den)
235     elsif other.kind_of?(Float)
236       Float(self) ** other
237     else
238       x, y = other.coerce(self)
239       x ** y
240     end
241   end
243   #
244   # Returns the remainder when this value is divided by +other+.
245   #
246   # Examples:
247   #   r = Rational(7,4)    # -> Rational(7,4)
248   #   r % Rational(1,2)    # -> Rational(1,4)
249   #   r % 1                # -> Rational(3,4)
250   #   r % Rational(1,7)    # -> Rational(1,28)
251   #   r % 0.26             # -> 0.19
252   #
253   def % (other)
254     value = (self / other).to_i
255     return self - other * value
256   end
258   #
259   # Returns the quotient _and_ remainder.
260   #
261   # Examples:
262   #   r = Rational(7,4)        # -> Rational(7,4)
263   #   r.divmod Rational(1,2)   # -> [3, Rational(1,4)]
264   #
265   def divmod(other)
266     value = (self / other).to_i
267     return value, self - other * value
268   end
270   #
271   # Returns the absolute value.
272   #
273   def abs
274     if @numerator > 0
275       Rational.new!(@numerator, @denominator)
276     else
277       Rational.new!(-@numerator, @denominator)
278     end
279   end
281   #
282   # Returns +true+ iff this value is numerically equal to +other+.
283   #
284   # But beware:
285   #   Rational(1,2) == Rational(4,8)          # -> true
286   #   Rational(1,2) == Rational.new!(4,8)     # -> false
287   #
288   # Don't use Rational.new!
289   #
290   def == (other)
291     if other.kind_of?(Rational)
292       @numerator == other.numerator and @denominator == other.denominator
293     elsif other.kind_of?(Integer)
294       self == Rational.new!(other, 1)
295     elsif other.kind_of?(Float)
296       Float(self) == other
297     else
298       other == self
299     end
300   end
302   #
303   # Standard comparison operator.
304   #
305   def <=> (other)
306     if other.kind_of?(Rational)
307       num = @numerator * other.denominator
308       num_a = other.numerator * @denominator
309       v = num - num_a
310       if v > 0
311         return 1
312       elsif v < 0
313         return  -1
314       else
315         return 0
316       end
317     elsif other.kind_of?(Integer)
318       return self <=> Rational.new!(other, 1)
319     elsif other.kind_of?(Float)
320       return Float(self) <=> other
321     elsif defined? other.coerce
322       x, y = other.coerce(self)
323       return x <=> y
324     else
325       return nil
326     end
327   end
329   def coerce(other)
330     if other.kind_of?(Float)
331       return other, self.to_f
332     elsif other.kind_of?(Integer)
333       return Rational.new!(other, 1), self
334     else
335       super
336     end
337   end
339   #
340   # Converts the rational to an Integer.  Not the _nearest_ integer, the
341   # truncated integer.  Study the following example carefully:
342   #   Rational(+7,4).to_i             # -> 1
343   #   Rational(-7,4).to_i             # -> -2
344   #   (-1.75).to_i                    # -> -1
345   #
346   # In other words:
347   #   Rational(-7,4) == -1.75                 # -> true
348   #   Rational(-7,4).to_i == (-1.75).to_i     # false
349   #
350   def to_i
351     Integer(@numerator.div(@denominator))
352   end
354   #
355   # Converts the rational to a Float.
356   #
357   def to_f
358     @numerator.to_f/@denominator.to_f
359   end
361   #
362   # Returns a string representation of the rational number.
363   #
364   # Example:
365   #   Rational(3,4).to_s          #  "3/4"
366   #   Rational(8).to_s            #  "8"
367   #
368   def to_s
369     if @denominator == 1
370       @numerator.to_s
371     else
372       @numerator.to_s+"/"+@denominator.to_s
373     end
374   end
376   #
377   # Returns +self+.
378   #
379   def to_r
380     self
381   end
383   #
384   # Returns a reconstructable string representation:
385   #
386   #   Rational(5,8).inspect     # -> "Rational(5, 8)"
387   #
388   def inspect
389     sprintf("Rational(%s, %s)", @numerator.inspect, @denominator.inspect)
390   end
392   #
393   # Returns a hash code for the object.
394   #
395   def hash
396     @numerator.hash ^ @denominator.hash
397   end
399   attr :numerator
400   attr :denominator
402   private :initialize
405 class Integer
406   #
407   # In an integer, the value _is_ the numerator of its rational equivalent.
408   # Therefore, this method returns +self+.
409   #
410   def numerator
411     self
412   end
414   #
415   # In an integer, the denominator is 1.  Therefore, this method returns 1.
416   #
417   def denominator
418     1
419   end
421   #
422   # Returns a Rational representation of this integer.
423   #
424   def to_r
425     Rational(self, 1)
426   end
428   #
429   # Returns the <em>greatest common denominator</em> of the two numbers (+self+
430   # and +n+).
431   #
432   # Examples:
433   #   72.gcd 168           # -> 24
434   #   19.gcd 36            # -> 1
435   #
436   # The result is positive, no matter the sign of the arguments.
437   #
438   def gcd(other)
439     min = self.abs
440     max = other.abs
441     while min > 0
442       tmp = min
443       min = max % min
444       max = tmp
445     end
446     max
447   end
449   #
450   # Returns the <em>lowest common multiple</em> (LCM) of the two arguments
451   # (+self+ and +other+).
452   #
453   # Examples:
454   #   6.lcm 7        # -> 42
455   #   6.lcm 9        # -> 18
456   #
457   def lcm(other)
458     if self.zero? or other.zero?
459       0
460     else
461       (self.div(self.gcd(other)) * other).abs
462     end
463   end
465   #
466   # Returns the GCD _and_ the LCM (see #gcd and #lcm) of the two arguments
467   # (+self+ and +other+).  This is more efficient than calculating them
468   # separately.
469   #
470   # Example:
471   #   6.gcdlcm 9     # -> [3, 18]
472   #
473   def gcdlcm(other)
474     gcd = self.gcd(other)
475     if self.zero? or other.zero?
476       [gcd, 0]
477     else
478       [gcd, (self.div(gcd) * other).abs]
479     end
480   end
483 class Fixnum
484   undef quo
485   # If Rational is defined, returns a Rational number instead of a Fixnum.
486   def quo(other)
487     Rational.new!(self,1) / other
488   end
489   alias rdiv quo
491   # Returns a Rational number if the result is in fact rational (i.e. +other+ < 0).
492   def rpower (other)
493     if other >= 0
494       self.power!(other)
495     else
496       Rational.new!(self,1)**other
497     end
498   end
500   unless defined? 1.power!
501     alias power! **
502     alias ** rpower
503   end
506 class Bignum
507   unless defined? Complex
508     alias power! **
509   end
511   undef quo
512   # If Rational is defined, returns a Rational number instead of a Bignum.
513   def quo(other)
514     Rational.new!(self,1) / other
515   end
516   alias rdiv quo
518   # Returns a Rational number if the result is in fact rational (i.e. +other+ < 0).
519   def rpower (other)
520     if other >= 0
521       self.power!(other)
522     else
523       Rational.new!(self, 1)**other
524     end
525   end
527   unless defined? Complex
528     alias ** rpower
529   end