如何将有理数格式化为十进制数?

4

给定一个任意大(或小)的有限小数表示的Rational(有理数),例如:

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

如何将它作为字符串获得其完整小数值?

上述数字的预期输出为:

"0.000030517578125"

to_f 显然不起作用:

r.to_f
#=> 3.0517578125e-05

而且sprintf需要我指定数字的位数:

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

2**15 #=> 32768 - Sagar Pandya
@sagarpandya82 谢谢,那是一个复制粘贴错误。 - Stefan
3个回答

1
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"

但是非常大的数字,例如:

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

如果您需要更好的东西,我认为您需要使用自己实现的“字符串分割”功能。

问题在于我事先不知道小数位数。 - Stefan
r.denominator * r.numerator would be 32768 using my example value. That's way to much. It will even result in an error once you reach 2**31: RangeError: integer 2147483648 too big to convert to `int' - Stefan
你认为你会有多少位小数?浮点数有精度限制,你不能超过这个限制,因此没有简单的方法来解决这个问题。你会有超过1000位小数吗?你需要更高的精度吗? - Andrés
我需要这个来“打印”有理数,也就是说我想要一个字符串,而不是一个浮点数。而且我不能使用一个固定的限制,因为这些数字是由用户提供的。 - Stefan
请注意,在我的最后一个示例(编辑2)中,如果您执行以下操作:r = BigDecimal("1") / (BigDecimal("2")**BigDecimal("99999999")),则会得到30,103,999个小数位。 - Andrés
不幸的是,这并不容易:BigDecimal#/截断了结果(我已经就此开了一个单独的问题)。BigDecimal#div可以工作,但需要明确的精度。 - Stefan

1

Bigdecimalto_s方法有一个"F"选项。然而,需要一些转换才能将这个有理数转化为正确的格式。

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

不幸的是,这仅限于浮点精度(尝试2 ** 30)。您可以通过直接转换r.to_d(您必须require“bigdecimal / util”)来避免此问题,但是BigDecimalRational#to_d再次需要明确的精度。 - Stefan

1

大多数十岁的孩子都知道如何做到这一点:使用长除法!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.%s x 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)。如果有理数是重复序列类型,则此方法永远不会终止。由于通常无法事先知道有理数属于哪种类型,因此修改该方法以首先确定有理数是否具有有限的十进制表示可能很有用。已知不能通过消除公共因素来减少(即最简形式)的有理数n/d仅当d可被25整除且不能被任何其他质数整除时才具有此属性。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 参考文献.


我也使用了长除法。幸运的是,我的所有数字都有有限的小数表示。 - Stefan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接