# Support classes for homogeneous treatment of BigDecimal and Num values by defining BigDecimal.context require 'bigdecimal' require 'bigdecimal/math' require 'singleton' # Context class with some of the Flt::Num context functionality, to allow the use of BigDecimal numbers # similarly to other Num values; this eases the implementation of functions compatible with either # Num or BigDecimal values. class Flt::BigDecimalContext include Singleton # TODO: Context class with precision, rounding, etc. (no singleton) def eval yield self end def num_class BigDecimal end def Num(*args) args = *args if args.size==1 && args.first.is_a?(Array) if args.size > 1 BigDecimal(Flt::DecNum(*args).to_s) else x = args.first case x when BigDecimal x when Rational BigDecimal(x.numerator.to_s)/BigDecimal(x.denominator.to_s) else BigDecimal(x.to_s) end end end def radix 10 end # NaN (not a number value) def nan BigDecimal('0')/BigDecimal('0') end def infinity(sign=+1) BigDecimal(sign.to_s)/BigDecimal('0') end def zero(sign=+1) BigDecimal("#{(sign < 0) ? '-' : ''}0") end def int_radix_power(n) 10**n end def exact? BigDecimal.limit == 0 end def precision BigDecimal.limit end ROUNDING_MODES = { BigDecimal::ROUND_UP=>:up, BigDecimal::ROUND_DOWN=>:down, BigDecimal::ROUND_CEILING=>:ceiling, BigDecimal::ROUND_FLOOR=>:floor, BigDecimal::ROUND_HALF_UP=>:half_up, BigDecimal::ROUND_HALF_DOWN=>:half_down, BigDecimal::ROUND_HALF_EVEN=>:half_even } def rounding ROUNDING_MODES[BigDecimal.mode(BigDecimal::ROUND_MODE, nil)] end # Sign: -1 for minus, +1 for plus, nil for nan (note that BigDecimal zero is signed) def sign(x) big_dec_sign = x.sign if big_dec_sign < 0 -1 elsif big_dec_sign > 0 +1 end end # Return copy of x with the sign of y def copy_sign(x, y) self_sign = sign(x) other_sign = y.is_a?(Integer) ? (y < 0 ? -1 : +1) : y.sign if self_sign && other_sign if self_sign == other_sign x else -x end else nan end end def split(x) sgn, d, _b, e = x.split [sgn<0 ? -1 : +1, d.to_i, e-d.size] end # Return the value of the number as an signed integer and a scale. def to_int_scale(x) sgn, d, _b, e = x.split c = d.to_i [sgn<0 ? -1 : c, -c, e-d.size] end def special?(x) x.nan? || x.infinite? end def plus(x) x end def minus(x) -x end def to_r(x) Support::Rationalizer.to_r(x) end def rationalize(x, tol = nil) tol ||= Flt::Tolerance([x.precs[0], Float::DIG].max,:sig_decimals) case tol when Integer Rational(*Support::Rationalizer.max_denominator(x, tol, BigDecimal)) else Rational(*Support::Rationalizer[tol].rationalize(x)) end end class << self def big_decimal_method(*methods) #:nodoc: methods.each do |method| if method.is_a?(Array) float_method, context_method = method else float_method = context_method = method end define_method(context_method) do |x| Num(x).send float_method end end end end big_decimal_method :nan?, :infinite?, :zero?, :abs end # Return a (limited) context object for BigDecimal. # This eases the implementation of functions compatible with either Num or BigDecimal values. def BigDecimal.context Flt::BigDecimalContext.instance end