提问人:Alec 提问时间:4/13/2023 更新时间:4/13/2023 访问量:162
为什么浮点数准确,但小数是错误的?
Why is float accurate but Decimal is wrong?
问:
由于显而易见的原因,我们习惯于浮动不准确。我认为小数应该是准确的,因为它们代表了所有以 10 为基数的数字
这段代码给了我们正确答案 9
print(27*3/9)
所以我想,哦,这是整数乘法,然后是除法,这就是为什么它是准确的
但是不,这给了我们正确的 9.0:
print(27*(3/9))
那么为什么会这样
print(Decimal(27)*(Decimal(3)/Decimal(9)))
给出不正确的 8.99999999999999999999999999
我知道 3/9 是 0.333...不能表示为终止小数。但是,为什么以 2 为基数的浮点数是准确的呢?
答:
正如回答您的问题的评论所述,您很幸运地找到了一个示例,该示例恰好使用 s 产生了确切的答案,而结果略有偏差。float
Decimal
正如您指出的,是 0.33 的重复性,因此两个方案都将近似。一般来说,这是正确的,因为大多数数字都不能由任何一个系统表示,但对于我们人类关心的数字来说,通常更容易推理。3/9
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()
方法,但它对这个问题没有多大帮助。我想你可以用它来看到它只是改变了小数部分的 LSB。nextafter
We're used to floats being inaccurate for obvious reasons.
The thing is that for the most part, those "obvious reasons" apply to decimal fractions, too.
You can't represent the fraction 1/3 as a decimal fraction. You can approximate it as 0.333, or 0.333333333, but no matter how many 3's you add at the end, it's never going to be exact. And if you multiply it by 3 again, you're liable to get 0.999999999, not 1.0.
You can't represent π = 3.141592654… exactly in either decimal or binary.
You can't represent √2 = 1.41421356… exactly in either decimal or binary.
You can't represent e = 2.718281828… exactly in either decimal or binary.
My point is that neither decimal nor binary has a monopoly on accuracy (or inaccuracy). It only seems like decimal is always right, and binary is often wrong, and the reason for that is just that we're so used to seeing decimal fractions, and we overlook their inaccuracies, but the inaccuracies that arise when we convert to/from binary always startle us.
Now, one way that decimal is "better" than binary is that, mathematically, there are no binary fractions that can't be converted exactly to decimal, while there are plenty of decimal fractions (most of them, actually) that can't be converted exactly to binary. That is, if you've got a binary fraction like , you can always convert it to an exact decimal fraction 1.328125, but if you've got even the simplest decimal fraction 0.1, when you try to convert it to binary you get an infinitely-repeating pattern .0b1.010101
0b0.0001100110011…
But this is all sort of by way of background, and doesn't answer your other question. Why does happen to give you an exact answer in binary, but not in decimal, even though isn't representable exactly in either decimal or binary? And the answer is just that roundoff error is kind of random, and sometimes, two roundoff errors cancel each other out. In IEEE-754 floating point, which is what Python is probably using, the closest double-precision value to 3/9 is a 53-bit binary fraction which works out to exactly 0.333333333333333314829616256247390992939472198486328125 . When you multiply that number by 27, the exact answer would be 8.999999999999999500399638918679556809365749359130859375. IEEE-754 says that when you multiply, the result you get (if inexact) must be a correctly-rounded version of the exact result, and that number is close enough to 9.0 that it does indeed get rounded up.27*(3/9)
3/9
I'm not sure how Python's Decimal type is implemented. Either it doesn't have the same rounded-actual-result guarantee, or it ends up happening that the exact result (in Decimal) is closer to 8.999999999999999999999999999 than it is to 9.0.
Footnote: I said that "roundoff error is kind of random", but that's not really true. A number theorist could tell us exactly which results are going to be exact and which are approximate, could tell us exactly when two roundoff errors would cancel each out and when they would persist. But I don't know enough about number theory to even try to make that argument.
评论
Decimal(3/9)
9.
Decimal
不是任意的精度格式,而是 is。它只是使用以 10 为基数而不是以 2 为基数来存储任意实数的近似值。float