Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / bigdecimal.rb
bloba326721b707697d26e0f49b4b041eb867e644a2e
1 # depends on: class.rb numeric.rb regexp.rb string.rb
3 def BigDecimal(string, _precs=0)
4   BigDecimal.new(string, _precs)
5 end
7 class BigDecimal < Numeric
8   # See stdlib/ext/bigdecimal for MatzRuby implementation.
9   
10   attr_reader :digits
11   protected :digits
12   
13   #############
14   # Constants #
15   #############
16   
17   SIGN_POSITIVE_ZERO = 1
18   SIGN_NEGATIVE_ZERO = -1
19   SIGN_POSITIVE_FINITE = 2
20   SIGN_NEGATIVE_FINITE = -2
21   SIGN_POSITIVE_INFINITE = 3
22   SIGN_NEGATIVE_INFINITE = -3
23   SIGN_NaN = 0 # is this correct?
24   
25   PLUS = '+'
26   MINUS = '-'
27   RADIX = '.'
28   EXP = 'E'
29   SIGNS = {-1 => MINUS, 0 => nil, 1 => PLUS}
30   
31   VERSION = "1.0.1" # like Ruby 1.8.6
32   
33   #################
34   # Class methods #
35   #################
36   
37   def self.ver
38     VERSION
39   end
40   
41   ###############################
42   # Constructor and basic tests #
43   ###############################
44   
45   # call-seq:
46   #   BigDecimal("3.14159")   => big_decimal
47   #   BigDecimal("3.14159", 10)   => big_decimal
48   def initialize(_val, _precs=0)
49     # set up defaults
50     @sign = PLUS
51     @digits = 0 # decimal point is assumed at beginning; exp is assigned on this basis
52     @exp = 0
53     @special = nil # 'n' for NaN, 'i' for Infinity, nil otherwise
55     v = _val.strip
56     if v == "NaN"
57       @special = 'n'
58       @precs = 0
59     elsif v =~ /^[-+]?Infinity$/
60       @special = 'i'
61       @sign = MINUS if v =~ /-/
62       @precs = 0
63     else
64       v = _val.gsub('_', '')
65       m = /^\s*(([-+]?)(\d*)(?:\.(\d*))?(?:[EeDd]([-+]?\d+))?).*$/.match(v)
66       if !m.nil?
67         @sign = m[2] unless m[2].to_s.empty?
68         int = m[3].to_s.gsub(/^0*/, '')
69         frac = m[4].to_s
70         fraczeros = /^0*/.match(frac)[0]
71         @exp = m[5].to_i + int.length
72         if int.to_i == 0 
73           @exp -= (fraczeros.size == frac.size) ? 0 : fraczeros.length
74         end
75         @digits = (int + frac).gsub(/0*$/, '').to_i
76       end
77       @precs = [v.length, _precs].max
78     end
79   end
81   # As for Float.finite? .
82   # call-seq:
83   #   BigDecimal.new("Infinity").finite?  => false
84   #   BigDecimal.new("NaN").finite?  => true
85   def finite?
86     @special != 'i' && !self.nan?
87   end
88   
89   def infinite?
90     if self.finite? or self.nan?
91       return nil
92     else
93       return (@sign + '1').to_i
94     end
95   end
97   # As for Float.nan? .
98   # call-seq:
99   #   BigDecimal.new("NaN").nan?  => true
100   #   BigDecimal.new("123").nan?  => false
101   def nan?
102     @special == 'n'
103   end
104   
105   # True if positive or negative zero; false otherwise.
106   # call-seq:
107   #   BigDecimal.new("0").zero?   =>true
108   #   BigDecimal.new("-0").zero?  =>true
109   def zero?
110     @digits.to_i == 0 and self.finite?
111   end
113   def precs
114     if !self.finite?
115       sigfigs = 0
116     else
117       sigfigs = @digits.to_s.length
118     end
119     [sigfigs, @precs]
120   end
121   
122   ###############
123   # Conversions #
124   ###############
125   
126   def to_f
127     if self.sign == SIGN_POSITIVE_INFINITE
128       return +1.0/0.0 
129     elsif self.sign == SIGN_NEGATIVE_INFINITE
130       return -1.0/0.0
131     elsif self.nan?
132       return 0.0/0.0
133     end
135     self.to_s("F").to_f
136   end
137   
138   def to_i
139     if !self.finite?
140       return nil
141     end
142     self.fix.to_s("F").to_i
143   end
145   def to_s(arg='')
146     # parse the argument for format specs
147     positive = case arg
148       when /\+/ then PLUS.clone
149       when / / then ' '
150       else ''
151     end
152     format = arg =~ /F/ ? :float : :eng
153     spacing = arg.to_i
154     
155     nan = 'NaN'
156     infinity = 'Infinity'
158     if self.nan?
159       return nan
160     end
162     if @sign == PLUS
163       str = positive
164     else
165       str = MINUS.clone
166     end
168     if self.finite?
169       value = @digits.to_s
170       if format == :float
171         # get the decimal point in place
172         if @exp >= value.length
173           value << ('0' * (@exp - value.length)) + RADIX + '0'
174         elsif @exp > 0
175           value = value[0, @exp] + RADIX + value[@exp..-1]
176         elsif @exp <= 0
177           value = '0' + RADIX + ('0' * -@exp) + value
178         end
179       elsif format == :eng
180         value = '0' + RADIX + value
181         if @exp != 0
182           value << EXP + @exp.to_s
183         end
184       end
185       
186       if spacing != 0
187         m = /^(\d*)(?:(#{RADIX})(\d*)(.*))?$/.match(value)
188         left, myradix, right, extra = m[1, 4].collect{|s| s.to_s}
189         right_frags = []
190         0.step(right.length, spacing) do |n|
191           right_frags.push right[n, spacing]
192         end
193         
194         left_frags = []
195         tfel = left.reverse
196         0.step(left.length, spacing) do |n|
197           left_frags.unshift tfel[n, spacing].reverse
198         end
199         
200         right = right_frags.join(' ').strip
201         left = left_frags.join(' ').strip
202         value = left.to_s + myradix.to_s + right.to_s + extra.to_s
203       end
204       str << value
205     else
206       str << infinity
207     end
208     return str
209   end
210   
211   def inspect
212     str = '#<BigDecimal:'
213     str << [nil, "'#{self.to_s}'", "#{precs[0]}(#{precs[1]})"].join(',')
214     str << '>'
215     return str
216   end
218   def coerce(other)
219     Ruby.primitive :numeric_coerce
220     if other.kind_of?(BigDecimal)
221       [other, self]
222     else
223       [BigDecimal(other.to_s), self]
224     end
225   end
227   #########################
228   # Arithmetic operations #
229   #########################
231   # These are stubbed out until we implement them so that their respective specfiles don't crash.
233   def add(other, precs)
234     if !other.kind_of?(BigDecimal)
235       return self.add(BigDecimal(other.to_s), precs)
236     elsif self.nan? or other.nan?
237       return BigDecimal("NaN")
238     elsif !self.finite? and !other.finite? and self.sign != other.sign
239       # infinity + -infinity
240       return BigDecimal("NaN")
241     elsif !self.finite? or other.zero?
242       return self
243     elsif !other.finite? or self.zero?
244       return other
245     elsif self.exponent == other.exponent
246       sd, od = self.align(other)
247       sum = (sd.to_i * (self.sign <=> 0)) + (od.to_i * (other.sign <=> 0))
248       s = sum.abs.to_s
249       sumdiff = s.length - sd.length
250       if sum < 0
251         s = MINUS + RADIX + s
252       else
253         s = RADIX + s
254       end
255       BigDecimal(s + EXP + (self.exponent + sumdiff).to_s, precs)
256     elsif self.exponent == 0 or other.exponent == 0
257       if self.exponent == 0
258         z = self
259         nz = other
260       else
261         z = other
262         nz = self
263       end
264       # so z is the one with the 0 exponent
265       zd = z.digits.to_s
266       nzd = nz.digits.to_s
267       nzx = nz.exponent
268       
269       if nzx > 0
270         zd = ('0' * nzx) + zd
271       else # if nzx < 0
272         nzd = ('0' * nzx.abs) + nzd
273       end
274       
275       zd, nzd = BigDecimal.align(zd, nzd)
277       l = zd.length
278       sum = (nzd.to_s.to_i * (nz.sign <=> 0)) + (zd.to_s.to_i * (z.sign <=> 0))
279       sumsign = sum < 0 ? MINUS : PLUS
280       s = sum.abs.to_s
281       sumdiff = s.length - zd.length
282       BigDecimal(sumsign + RADIX + s + EXP + (sumdiff + [nzx, 0].max).to_s, precs)
283     else
284       a, b, extra = reduce(self, other)
285       sum = a + b
286       BigDecimal(SIGNS[sum.sign <=> 0].to_s + RADIX + sum.digits.to_s + EXP + (sum.exponent + extra).to_s, precs)
287     end
288   end
289   
290   def +(other)
291     self.add(other, 0)
292   end
293   
294   def sub(other, precs)
295     self.add(-other, precs)
296   end
298   def -(other)
299     self + -other
300   end
301   
302   def mult(other, precs)
303     if !other.kind_of?(BigDecimal)
304       return self.mult(BigDecimal(other.to_s), precs)
305     elsif (self.infinite? and other.zero?) or (self.zero? and other.infinite?)
306       return BigDecimal("NaN")
307     elsif (self.nan? or other.nan?)
308       return BigDecimal("NaN")
309     elsif (self.zero? or other.zero?)
310       return BigDecimal("0") if (self.sign * other.sign) > 0
311       return BigDecimal("-0") if (self.sign * other.sign) < 0
312     elsif !self.finite?
313       if (self.sign * other.sign < 0) == self.sign < 0
314         return self
315       else
316         return -self
317       end
318     elsif !other.finite?
319       if (self.sign * other.sign < 0) == self.sign < 0
320         return other
321       else
322         return -other
323       end
324     end
325     
326     sd = self.digits
327     od = other.digits
328     
329     # figure out how many decimal places we're dealing with
330     sp = sd.to_s.length - self.exponent
331     op = od.to_s.length - other.exponent
332     
333     a = sd * (self < 0 ? -1 : 1)
334     b = od * (other < 0 ? -1 : 1)
335     prod = a * b
336     pa = prod.abs
337     BigDecimal([SIGNS[prod <=> 0], RADIX, pa, EXP, pa.to_s.length - (sp + op)].join)
338   end
339   
340   def *(other)
341     self.mult(other, 0)
342   end
344   def quo(other)
345     self.div(other, 0)
346   end
347   alias / quo
348   
349   def div(other, precs = nil)
350     if !other.kind_of?(BigDecimal)
351       self.quo(BigDecimal(other.to_s))
352     elsif self.nan? or other.nan?
353       return BigDecimal("NaN")
354     elsif other.infinite?
355       if precs.nil? or self.infinite?
356         return BigDecimal("NaN") 
357       else
358         return BigDecimal("0")
359       end
360     elsif other.zero?
361       return BigDecimal("NaN") if precs.nil? or self.zero?
362       return BigDecimal("Infinity") * other.sign
363     elsif (other.digits == 1) and self.infinite?
364       if precs.nil?
365         return BigDecimal("NaN")
366       else
367         return self * other.sign
368       end
369     elsif !self.exponent.zero? and !other.exponent.zero?
370       a, b, extra = reduce(self, other)
371       q = precs.nil? ? (a / b).floor : (a / b)
372       p = precs.nil? ? q.to_s.length : precs
373       p.zero? ? BigDecimal(q.to_s) : BigDecimal(q.to_s[0..p+1])
374     else
375       sa, oa = self.align(other)
376       q = [SIGNS[self <=> 0], sa].join.to_f / [SIGNS[other <=> 0], oa].join.to_f
377       BigDecimal([q, EXP, self.exponent - other.exponent].join)
378     end    
379   end
381   def remainder(other)
382     mod = self % other
384     if (self.sign * other.sign < 0)
385       return mod - other
386     else
387       return mod
388     end
389   end
391   def modulo(other)
392     self.divmod(other)[1]
393   end
394   alias % modulo
395   
396   def divmod(other)
397     arr = []
399     raise TypeError if other.kind_of?(String)
400     other = BigDecimal(other.to_s) if other.kind_of?(Integer)
402     if self.infinite? or self.nan?
403       return [BigDecimal("NaN"), BigDecimal("NaN")]
404     end
406     if other.infinite? or other.nan? or other.zero?
407       return [BigDecimal("NaN"), BigDecimal("NaN")]
408     end
410     first = (self / other).floor     
411     second = self - (first * other)
413     arr << first.to_i << second.to_f if other.kind_of?(Float)
414     arr << first << second
415   end
416   
417   def sqrt(other)
418   end
420   # Raises self to an integer power.
421   def power(other)
422     one = BigDecimal("1")
423     if !self.finite?
424       return BigDecimal("NaN")
425     elsif other.zero? or self == 1
426       return one
427     elsif self.zero?
428       if other > 0
429         return BigDecimal("0")
430       else
431         return BigDecimal("Infinity")
432       end
433     elsif other < 0
434       return one / (self ** other.abs)
435     elsif !self.exponent.zero?
436       base = BigDecimal([@sign, RADIX, @digits].join)
437       exp = self.exponent
438       n = base ** other
439       return BigDecimal([SIGNS[n <=> 0], RADIX, n.digits, EXP, (exp * other) + n.exponent].join)
440     elsif other == 1
441       return self
442     elsif other % 2 == 1
443       return self * (self ** (other - 1))
444     else
445       return (self * self) ** (other / 2)
446     end
447   end
448   alias ** power
449   
450   # Unary minus
451   def -@
452     if self.nan?
453       return self
454     end
455     s = self.to_s
456     if @sign == MINUS
457       BigDecimal(s[1..-1])
458     else
459       BigDecimal(MINUS + s)
460     end
461   end
463   def <=>(other)
464     if other.nil?
465       return nil
466     elsif !other.kind_of?(BigDecimal)
467       return self <=> self.coerce(other)[0]
468     elsif self.nan? or other.nan?
469       return nil
470     elsif self.eql?(other)
471       return 0
472     else
473       result = (self.sign <=> other.sign).nonzero? || \
474       (self.exponent <=> other.exponent).nonzero? || \
475       (self.to_i <=> other.to_i).nonzero? || \
476       ((self - other).sign <=> BigDecimal("0").sign)
477       return result
478     end
479   end
480   
481   # Apparently, 'include Comparable' doesn't work, so:
482   def >(other)
483     compare_method(other, 1)
484   end
485   
486   def >=(other)
487     return (self > other or self == other)
488   end
489   
490   def <(other)
491     compare_method(other, -1)
492   end
493   
494   def <=(other)
495     return (self < other or self == other)
496   end
497   
498   def ==(other)
499     compare_method(other, 0)
500   end
502   def eql?(other)
503     if self.nan?
504       return false
505     elsif other.respond_to?(:nan?) and other.nan?
506       return false
507     elsif self.zero? and other.respond_to?(:zero?)
508       return other.zero?
509     elsif self.to_s == other.to_s
510       return true
511     elsif !other.kind_of?(BigDecimal)
512       return self.eql?(BigDecimal(other.to_s))
513     else
514       return false
515     end
516   end
517   alias === eql?
518   
519   ####################
520   # Other operations #
521   ####################
522   
523   # I'm trying to keep these in alphabetical order unless a good reason develops to do otherwise.
524   
525   def abs
526     if self.nan? or @sign == PLUS
527       return self
528     else
529       s = self.to_s.sub(/^-/, '') # strip minus sign
530       BigDecimal(s)
531     end
532   end
533   
534   def ceil(n = 0)
535     if self.infinite?
536       return self
537     elsif !n.zero?
538       x = (BigDecimal([@sign, '0', RADIX, @digits, EXP, self.exponent + n].join)).ceil
539       return BigDecimal([@sign, '0', RADIX, x.digits, EXP, x.exponent - n].join)
540     elsif self.frac.zero?
541       return self
542     elsif self < 0
543       return self.fix
544     else
545       return self.fix + BigDecimal("1")
546     end
547   end
548   
549   # Returns the exponent as a Fixnum (or 0 if out of range), such that the absolute value of the base is between 0 and 1.  This is not the power function.
550   # call-seq:
551   #   BigDecimal("0.125e3").exponent => 3
552   #   BigDecimal("3000").exponent => 4
553   #
554   def exponent
555     return @exp
556   end
557   
558   def fix
559     d = @digits.to_s.length
560     if !self.finite? or d <= @exp
561       return self
562     elsif @exp < 0
563       return BigDecimal("#{@sign}0")
564     end
565     s = self.to_s("F").split(RADIX)[0] # this includes the sign
566     BigDecimal(s)
567   end
568   
569   def floor(n = 0)
570     -((-self).ceil(n))
571   end
572   
573   def frac
574     if !self.finite?
575       return self
576     elsif @digits.to_s.length <= @exp
577       return BigDecimal("0")
578     end
579     s = self.to_s("F").split(RADIX)[1] # the part after the decimal point
580     BigDecimal(@sign + RADIX + s)
581   end
582   
583   def sign
584     if self.nan?
585       SIGN_NaN
586     elsif self.zero?
587       @sign == PLUS ? SIGN_POSITIVE_ZERO : SIGN_NEGATIVE_ZERO
588     elsif self.finite?
589       @sign == PLUS ? SIGN_POSITIVE_FINITE : SIGN_NEGATIVE_FINITE
590     else # infinite
591       @sign == PLUS ? SIGN_POSITIVE_INFINITE : SIGN_NEGATIVE_INFINITE
592     end
593   end
595   def split
596     arr = []
597     base = 10
599     if self.sign > 0 
600       sgn =  1
601     elsif self.sign < 0
602       sgn = -1
603     else
604       sgn = 0    
605     end
607     if self.infinite? 
608       value = "Infinity"
609     elsif self.nan?
610       value = "NaN"
611     else
612       value = @digits.to_s
613     end
615     arr << sgn << value << base << @exp
616   end
617   
618   def truncate(prec = nil)
619     if !self.finite?
620       return self
621     elsif prec.nil?
622       self.fix
623     else
624       e = [0, @exp + prec].max
625       s = @digits.to_s[0, e]
626       BigDecimal(@sign + '0' + RADIX + s + EXP + @exp.to_s)
627     end
628   end
629   
630   ############################
631   # Internal utility methods #
632   ############################
633   
634  protected
636   # Takes two BigDecimals and returns an array of their digit strings,
637   # with the shorter one right-padded with zeros so they're the same length.
638   # Can also take a digit string itself.
639   # call-seq:
640   #   BigDecimal("12").align(BigDecimal("0.0056789")) => ["12000", "56789"]
641   #   BigDecimal("8.765").align("43") => ["8765", "4300"]
642   def align(y) #:nodoc:
643     xd = self.digits.to_s
644     yd = y.kind_of?(BigDecimal) ? y.digits.to_s : y
645     BigDecimal.align(xd, yd)
646   end
647   
648   # Like BigDecimal#align, but can take two digit strings.
649   # call-seq:
650   #   BigDecimal.align("8765", "43") => ["8765", "4300"]
651   def self.align(x, y) #:nodoc:
652     xd = x.clone
653     yd = y.clone
654     diff = xd.length - yd.length
655     if diff > 0
656       yd << '0' * diff
657     else
658       xd << '0' * diff.abs
659     end
660     [xd, yd]
661   end
662   
663   # Wrapper for implementing comparison methods.
664   def compare_method(other, val)
665   #  if !self.nan? and other.respond_to?(:nan?) and other.nan?
666   #    raise ArgumentError, "Can't compare #{self} to NaN", caller
667   #  else
668       result = (self <=> other)
669       return result.nil? ? nil : result == val
670   #  end
671   end
672   
673   # Reduces exponents and returns [a, b, extra].
674   # call-seq:
675   # reduce(BigDecimal("8E5"), BigDecimal("6E2")) => [BigDecimal("8E3"), BigDecimal("6"), 2]
676   def reduce(x, y)
677     extra = [x.exponent, y.exponent].min
678     a = BigDecimal(SIGNS[x.sign <=> 0].to_s + RADIX + x.digits.to_s + EXP + (x.exponent - extra).to_s)
679     b = BigDecimal(SIGNS[y.sign <=> 0].to_s + RADIX + y.digits.to_s + EXP + (y.exponent - extra).to_s)
680     [a, b, extra]
681   end