如何将Rational数字格式化为十进制?

给定具有有限十进制表示的任意大(或小) Rational ,例如:

 r = Rational(1, 2**15) #=> (1/32768) 

如何将其完整十进制值作为字符串?

上述数字的预期输出为:

 "0.000030517578125" 

to_f显然不起作用:

 r.to_f #=> 3.0517578125e-05 

并且sprintf要求我指定位数:

 sprintf('%.30f', r) #=> "0.000030517578125000000000000000" 

大多数十岁的孩子都知道如何做到这一点:使用长师! 1

 def finite_long_division(n,d) return nil if d.zero? sign = n*d >= 0 ? '' : '-' n, d = n.abs, d.abs pwr = case n <=> d when 1 then power(n,d) when 0 then 0 else -power(d,n)-1 end n *= 10**(-pwr) if pwr < 0 d *= 10**(pwr) if pwr >= 0 s = ld(n,d) t = s.size == 1 ? '0' : s[1..-1] "%s%s.%sx 10^%d" % [sign, s[0], t, pwr] end def power(n, d) # n > d ns = n.to_s ds = d.to_s pwr = ns.size - ds.size - 1 pwr += 1 if ns[0, ds.size].to_i >= ds.to_i pwr end def ld(n,d) s = '' loop do # .with_object('') do |s| m,n = n.divmod(d) s << m.to_s return s if n.zero? n *= 10 end end 

例子 2

 finite_long_division(1, 2**15) #=> "3.0517578125 x 10^-5" finite_long_division(-1, 2**15) #=> "-3.0517578125 x 10^-5" finite_long_division(-1, -2**15) #=> "3.0517578125 x 10^-5" finite_long_division(143, 16777216) #=> "8.523464202880859375 x 10^-6" 143/16777216.0 #=> 8.52346420288086e-06 finite_long_division(8671, 803469022129495137770981046170581301261101496891396417650688) #=> "1.079195309486679194852923588206549145803161531099624\ # 804222395643336829571798416196370119711226461255452\ # 67714596064934085006825625896453857421875 x 10^-56" 

回想一下,每个有理数都有十进制表示或包含无限重复的数字序列(例如, 1/3 #=> 0.33333...3227/555 #=> 5.8144144144...1/9967 #=> 0.00010033109260559848... 3 )。 因此,如果理性是重复序列变种,则该方法永远不会终止。 由于通常事先不知道有理数是哪种类型,因此修改该方法以首先确定有理数是否具有有限的十进制表示可能是有用的。 众所周知,当且仅当d可被25整除并且不能被任何其他素数整除时,不能减少的有理数n/d (通过去除公因子)具有这种性质。 4我们可以很容易地构造一种方法来确定已经减少的有理数是否具有该属性。

 require 'prime' def decimal_representation?(n, d) primes = Prime.prime_division(d).map(&:first) (primes & [2,5]).any? && (primes - [2, 5]).empty? end 

1至少在我小时候这是真的。

2有关具有有限小数表示的有理数的部分列表,请参见此处

3此有理数的重复序列包含9,966个数字。

4 参考

 a = sprintf('%.30f', r) a.gsub(/0*\z/,'') 

这就是全部:)(或应该:P)这不是最好的方法,如果值超过30个小数,则需要在sprintf中添加30个以上的零。 我认为有更好的方法可以做到这一点,但这种方式有效

编辑

 require 'bigdecimal' require 'bigdecimal/util' b = BigDecimal.new(r, (r.denominator * r.numerator)) b.to_digits 

注意这个解决方案。 (r.denominator * r.numerator)这是精度,精度永远不会大于分母*分子(我想,但是数学家可以告诉你这个)

编辑2

 r = BigDecimal("1") / (BigDecimal("2") ** BigDecimal("99")) r.to_digits # Example r = BigDecimal("1") / (BigDecimal("2")**BigDecimal("99")) r.to_digits # "0.000000000000000000000000000001577721810442023610823457130565572459346412870218046009540557861328125" 

但reeeeelly大数字,如:

 r = BigDecimal("1") / (BigDecimal("2")**BigDecimal("999999999999")) # RangeError: integer 999999999999 too big to convert to `int' 

如果你需要更好的东西,我认为你需要自己实现“字符串划分”。

Bigdecimal to_s有一个“F”选项。 然而,需要一些转换来使这种理性成形。

 require "bigdecimal" r = Rational(1, 2**15) p BigDecimal.new(r.to_f.to_s).to_s("F") # => "0.000030517578125"