如何防止BigDecimal截断结果?

3

此问题之后:

我想计算1/1048576并获得正确的结果,即0.00000095367431640625。

使用BigDecimal/会截断结果:

require 'bigdecimal'
a = BigDecimal.new(1)
#=> #<BigDecimal:7fd8f18aaf80,'0.1E1',9(27)>
b = BigDecimal.new(2**20)
#=> #<BigDecimal:7fd8f189ed20,'0.1048576E7',9(27)>

n = a / b
#=> #<BigDecimal:7fd8f0898750,'0.9536743164 06E-6',18(36)>

n.to_s('F')
#=> "0.000000953674316406" <- should be ...625

这让我感到惊讶,因为我以为 BigDecimal 应该可以正常工作。

要得到正确的结果,我必须使用带有显式精度的 div

n = a.div(b, 100)
#=> #<BigDecimal:7fd8f29517a8,'0.9536743164 0625E-6',27(126)>

n.to_s('F')
#=> "0.00000095367431640625" <- correct

但我真的不太理解那个 precision(精确度) 参数。为什么我要指定它,以及我需要使用什么值才能得到未截断的结果?

这甚至是否有资格作为“任意精度浮点十进制算术”

此外,如果我通过以下方式计算上述值:

a = BigDecimal.new(5**20)
#=> #<BigDecimal:7fd8f20ab7e8,'0.9536743164 0625E14',18(27)>
b = BigDecimal.new(10**20)
#=> #<BigDecimal:7fd8f2925ab8,'0.1E21',9(36)>

n = a / b
#=> #<BigDecimal:7fd8f4866148,'0.9536743164 0625E-6',27(54)>

n.to_s('F')
#=> "0.00000095367431640625"

我得到了正确的结果。为什么?

类似于(1.0/3).is_a?(Float) #=> trueBigDecimal.new(1)/BigDecimal.new(3)可以写成BigDecimal.new(1)/3((BigDecimal.new(1)/3).is_a?(BigDecimal) #=> true),甚至是BigDecimal.new(1)/Rational(3) - Cary Swoveland
n.to_s('F') 返回比 n 更高的精度,这表明 BigDecimal 对象包含更多的信息。你想花时间研究 BigDecimal 吗?一个好的起点是 BigDecimal#to_s。另外,你能解释一下 (BigDecimal.new(1)/3).finite? #=> true 的意思吗? - Cary Swoveland
1
@CarySwoveland "你能解释一下(BigDecimal.new(1)/3).finite? #=> true的意思吗?" - 根据文档,只有NANINFINITY返回false - Stefan
关于精度,我只是指n=a/b返回10个有效数字,而n.to_s返回12个。请忘记我提到“有限”的参考。解释我脑海中的胡言乱语需要太多的话,而且会非常无聊。 - Cary Swoveland
@CarySwoveland inspect 还显示了 12 个数字,最后两个数字由一个空格分隔,就在 E-6 的前面,即:'0.9536743164 06E-6' = 0.953674316406 × 10⁻⁶。 - Stefan
显示剩余2条评论
1个回答

3

BigDecimal可以执行任意精度的浮点十进制计算,但它无法自动确定给定计算的“正确”精度。

例如,考虑以下情况:

BigDecimal.new(1)/BigDecimal.new(3)
# <BigDecimal:1cfd748, '0.3333333333 33333333E0', 18(36)>

可以说,在这种情况下没有正确的精度;使用正确的值取决于计算所需的准确性。值得注意的是,在数学意义上,几乎所有的整数除法都会导致一个具有无限小数扩展的数字,因此需要进行四舍五入。只有当分数在约分后,分母的唯一质因数是2和5时,它才有一个有限的表示。

因此,您必须指定精度。不幸的是,精度参数有点奇怪,因为它似乎既是有效数字的数量,又是小数点后的数字数量。以下是对变化精度的1/1048576的示例:

1   0.000001
2   0.00000095
3   0.000000953
9   0.000000953
10  0.0000009536743164
11  0.00000095367431641
12  0.000000953674316406
18  0.000000953674316406
19  0.00000095367431640625

对于小于10的任何值,BigDecimal会将结果截断为9位数字,这就是为什么在精度达到10时会突然出现精度提高的原因:此时它会切换到截断为18位数字(然后舍入到10个有效数字)。


† 取决于您比较可数无限集合大小的舒适程度。


这解释了为什么我可能需要为某些计算指定精度(尽管我的示例有一个有限的结果)。但它并没有真正解释“我必须使用什么值才能获得未截断的结果”这部分。例如,为什么 12 到 18 返回相同的结果,而 19 突然返回两个额外的数字? - Stefan
此外,如果我使用5^20/10^20而不是1/2^20来计算结果,即BigDecimal.new(5**20) / BigDecimal.new(10**20),我确实得到了正确的结果。你能解释一下吗? - Stefan
@Stefan,老实说,我并不完全理解精度参数是如何工作的。但对于你提供的例子,除以十的任何幂次方都将始终给出准确的结果,因为BigDecimals存储为整数乘以十的幂次方。 - Max

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