我们习惯于由于明显的原因而使浮点运算不准确。我认为小数应该是准确的,因为它们代表了所有基本的10位
这个代码给了我们9 的正确答案
print(27*3/9)
所以我想,哦,这是整数乘法,然后是除法,这就是为什么它是准确的
但没有,这给了我们正确的9.0:
print(27*(3/9))
那么为什么
print(Decimal(27)*(Decimal(3)/Decimal(9)))
给出不正确的8.999999999999999999999
我知道3/9是0.333…不能用终止小数表示。但为什么基数2浮动准确呢?
我们习惯于由于明显的原因而使浮点运算不准确。
问题是,在大多数情况下;显而易见的原因";也适用于小数。
你不能把1/3的分数表示为十进制分数。你可以将其近似为0.333或0.333333333,但无论你在最后加多少个3,它都不会是准确的。如果你再乘以3,你可能会得到0.999999999,而不是1.0。
π=3.141592654不能精确地用十进制或二进制表示
您不能精确地用十进制或二进制表示√2=1.41421356
您不能精确地用十进制或二进制表示e=2.718281828。
我的观点是,十进制和二进制都不能垄断准确性(或不准确性)。只有看起来像十进制总是正确的,而二进制经常是错误的,原因只是我们太习惯于看到小数,我们忽略了它们的不准确,但当我们转换为二进制时出现的不准确总是让我们感到震惊。
现在,十进制的一种方式是";"更好";比二进制更重要的是,从数学上讲,没有二进制分数不能准确地转换为十进制,而有很多十进制分数(实际上大多数)不能准确地转化为二进制。也就是说,如果你有一个像0b1.010101
这样的二进制分数,你总是可以把它转换成精确的十进制分数1.328125,但如果你有最简单的十进制分数0.1,当你试图把它转换为二进制时,你会得到一个无限重复的模式0b0.0001100110011…
。
但这都是背景问题,并不能回答你的其他问题。为什么27*(3/9)
恰好用二进制给出了确切的答案,而不是用十进制给出的,尽管3/9
不能用十进制或二进制精确表示?答案是舍入误差是随机的,有时两个舍入误差会相互抵消。在IEEE-754浮点(Python可能正在使用)中,最接近3/9的双精度值是一个53位二进制分数,精确到0.333333333333314829616256247390992939472198486328125。当你把这个数字乘以27时,确切的答案是8.99999999999999950039638918679556809365749359130859375。IEEE-754规定,当你相乘时,你得到的结果(如果不精确)必须是精确结果的正确四舍五入版本,并且这个数字足够接近9.0,所以它确实会被四舍五进。
我不确定Python的Decimal类型是如何实现的。要么它没有相同的四舍五入实际结果保证,要么它最终发生的确切结果(十进制)比9.0更接近8.999999999999999999999。
脚注:我说过";舍入误差是随机的;,但事实并非如此。数论者可以准确地告诉我们哪些结果是准确的,哪些是近似的,可以准确地告知我们两个舍入误差何时会相互抵消,以及它们何时会持续。但我对数论的了解还不够,甚至无法提出这样的论点。
正如回答您的问题的评论所说,您只是幸运地找到了一个使用float
s生成准确答案的示例,而Decimal
的结果略有不同。
正如您所指出的,3/9
是0.33,因此将通过两种方案进行近似。一般来说,这是真的,因为大多数数字都不能用任何一个系统来表示,但对于我们人类关心的数字,Decimal
通常更容易推理。
有两个有用的工具可以帮助理解这里发生的事情,Decimal(float(value))
将为您提供value
的全十进制扩展,math.nextafter(value, math.inf)
将为您在value
之后提供下一个可表示的浮点值。
要查看计算中的内容,可以在计算中间值时查看这些值。我会按照以下方式进行:
from decimal import Decimal as D
print(D(1) / 3, D(1 / 3), sep='n')
这应该打印出来:
0.3333333333333333333333333333
0.333333333333333314829616256247390992939472198486328125
十进制输出确实更接近正确答案,因为它使用了更多的状态来表示值。
然后我们可以乘以27:
print(D(1) / 3 * 27, D(1 / 3) * 27, sep='n')
打印输出:
8.999999999999999999999999999
8.999999999999999500399638919
它们现在都被截断到上下文的精度,但可以";参见内部";中间体";浮动";值,然后四舍五入到最接近的可表示值。
使用math.nextafter
,我们可以检查附近的值,以了解舍入操作是否正确执行:
from math import nextafter, inf
print(D(nextafter(9, -inf)), D(1 / 3) * 27, D(nextafter(9, inf)), sep='n')
正如您所看到的,中间的中间值比任何一个最接近的可表示值都更接近9。因此,浮点单位将选择9作为结果,这就是为什么它似乎从这个计算中给出了正确的结果,即使1/3的中间值不是最接近的。
通常,在每个浮点运算结束时执行的四舍五入到最接近的可表示值可能会引入一些错误,但希望这些工具能帮助您了解内部发生了什么。
现代语言提供的另一个有用的工具是浮点的十六进制表示。Python将其公开为hex()
方法,但它对这个问题没有太大帮助。我想你可以用它来看看nextafter
只是改变了分数部分的LSB。