15. Floating Point Arithmetic: Issues and Limitations浮点运算:问题和局限性¶
Floating-point numbers are represented in computer hardware as base 2 (binary) fractions. 浮点数在计算机硬件中以2进制(二进制)分数表示。For example, the decimal fraction例如,小数
0.125
has value 1/10 + 2/100 + 5/1000, and in the same way the binary fraction具有1/10+2/100+5/1000的值,并且以相同的方式表示二进制分数
0.001
has value 0/2 + 0/4 + 1/8. These two fractions have identical values, the only real difference being that the first is written in base 10 fractional notation, and the second in base 2.值为0/2+0/4+1/8。这两个分数有相同的值,唯一真正的区别是第一个分数是以10为基数写的,第二个分数是以2为基数写的。
Unfortunately, most decimal fractions cannot be represented exactly as binary fractions. 不幸的是,大多数十进制分数不能精确地表示为二进制分数。A consequence is that, in general, the decimal floating-point numbers you enter are only approximated by the binary floating-point numbers actually stored in the machine.其结果是,通常情况下,您输入的十进制浮点数仅与机器中实际存储的二进制浮点数近似。
The problem is easier to understand at first in base 10. 首先,在10进制中,这个问题更容易理解。Consider the fraction 1/3. 考虑分数1/3。You can approximate that as a base 10 fraction:你可以将其近似为以10为基数的分数:
0.3
or, better,或者更好,
0.33
or, better,或者更好,
0.333
and so on. 等等No matter how many digits you’re willing to write down, the result will never be exactly 1/3, but will be an increasingly better approximation of 1/3.不管你愿意写多少个数字,结果永远不会精确到1/3,而是越来越接近1/3。
In the same way, no matter how many base 2 digits you’re willing to use, the decimal value 0.1 cannot be represented exactly as a base 2 fraction. 同样,无论你愿意使用多少个以2为基数的数字,十进制值0.1都不能精确地表示为以2为基数的分数。In base 2, 1/10 is the infinitely repeating fraction在基数2中,1/10是无限重复的分数
0.0001100110011001100110011001100110011001100110011...
Stop at any finite number of bits, and you get an approximation. 在任何有限位数处停止,你就会得到一个近似值。On most machines today, floats are approximated using a binary fraction with the numerator using the first 53 bits starting with the most significant bit and with the denominator as a power of two. 在今天的大多数机器上,浮点数是用二进制分数来近似的,分子使用前53位,从最高有效位开始,分母是二的幂。In the case of 1/10, the binary fraction is 在1/10的情况下,二进制分数为3602879701896397 / 2 ** 55
which is close to but not exactly equal to the true value of 1/10.3602879701896397 / 2 ** 55
,接近但不完全等于1/10的真值。
Many users are not aware of the approximation because of the way values are displayed. 由于显示值的方式,许多用户不知道近似值。Python only prints a decimal approximation to the true decimal value of the binary approximation stored by the machine. Python只打印机器存储的二进制近似值的真正十进制近似值。On most machines, if Python were to print the true decimal value of the binary approximation stored for 0.1, it would have to display在大多数机器上,如果Python要打印为0.1存储的二进制近似值的真正十进制值,它必须显示
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
That is more digits than most people find useful, so Python keeps the number of digits manageable by displaying a rounded value instead这比大多数人认为有用的数字还要多,因此Python通过显示一个四舍五入的值来保持数字的可控性
>>> 1 / 10
0.1
Just remember, even though the printed result looks like the exact value of 1/10, the actual stored value is the nearest representable binary fraction.请记住,尽管打印的结果看起来像1/10的精确值,但实际存储的值是最接近的可表示二进制分数。
Interestingly, there are many different decimal numbers that share the same nearest approximate binary fraction. 有趣的是,有许多不同的十进制数共享相同的近似二进制分数。For example, the numbers 例如,数字0.1
and 0.10000000000000001
and 0.1000000000000000055511151231257827021181583404541015625
are all approximated by 3602879701896397 / 2 ** 55
. 0.1
、0.100000000000001
和0.10000000000000555115123125782702118158340451015625
都近似于3602879701896397/2**55
。Since all of these decimal values share the same approximation, any one of them could be displayed while still preserving the invariant 由于所有这些十进制值共享相同的近似值,因此可以显示其中任何一个,同时仍保留不变的eval(repr(x)) == x
.eval(repr(x)) == x
。
Historically, the Python prompt and built-in 从历史上看,Python提示符和内置的repr()
function would choose the one with 17 significant digits, 0.10000000000000001
. repr()
函数会选择具有17个有效数字的一个,即0.100000000000001
。Starting with Python 3.1, Python (on most systems) is now able to choose the shortest of these and simply display 从Python3.1开始,Python(在大多数系统上)现在可以选择其中最短的一个,并只显示0.1
.0.1
。
Note that this is in the very nature of binary floating-point: this is not a bug in Python, and it is not a bug in your code either. 请注意,这正是二进制浮点的本质:这不是Python中的错误,也不是代码中的错误。You’ll see the same kind of thing in all languages that support your hardware’s floating-point arithmetic (although some languages may not display the difference by default, or in all output modes).在支持硬件浮点运算的所有语言中,您都会看到相同的情况(尽管某些语言在默认情况下或在所有输出模式下可能不会显示差异)。
For more pleasant output, you may wish to use string formatting to produce a limited number of significant digits:为了获得更令人愉快的输出,您可能希望使用字符串格式来生成有限数量的有效数字:
>>> format(math.pi, '.12g') # give 12 significant digits
'3.14159265359'
>>> format(math.pi, '.2f') # give 2 digits after the point
'3.14'
>>> repr(math.pi)
'3.141592653589793'
It’s important to realize that this is, in a real sense, an illusion: you’re simply rounding the display of the true machine value.重要的是要认识到,从真正意义上讲,这是一种错觉:你只是在对真实机器值的显示进行取整。
One illusion may beget another. 一种幻觉可能引发另一种幻觉。For example, since 0.1 is not exactly 1/10, summing three values of 0.1 may not yield exactly 0.3, either:例如,由于0.1不完全是1/10,因此将三个值相加0.1可能不会得到精确的0.3,或者:
>>> .1 + .1 + .1 == .3
False
Also, since the 0.1 cannot get any closer to the exact value of 1/10 and 0.3 cannot get any closer to the exact value of 3/10, then pre-rounding with 此外,由于0.1无法更接近1/10的精确值,0.3也无法更接近3/10的精确值,因此使用round()
function cannot help:round()
函数进行预舍入也无济于事:
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
Though the numbers cannot be made closer to their intended exact values, the 虽然无法使数字更接近其预期的精确值,但round()
function can be useful for post-rounding so that results with inexact values become comparable to one another:round()
函数可用于后舍入,以便具有不精确值的结果可以相互比较:
>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
Binary floating-point arithmetic holds many surprises like this. 二进制浮点算法有很多这样的惊喜。The problem with “0.1” is explained in precise detail below, in the “Representation Error” section. “0.1”的问题将在下面的“表示错误”部分详细解释。See The Perils of Floating Point for a more complete account of other common surprises.有关其他常见意外情况的更完整说明,请参阅浮点的危险。
As that says near the end, “there are no easy answers.” 正如接近尾声时所说,“没有简单的答案。”Still, don’t be unduly wary of floating-point! 不过,不要过度警惕浮点运算!The errors in Python float operations are inherited from the floating-point hardware, and on most machines are on the order of no more than 1 part in 2**53 per operation. Python浮点运算中的错误是从浮点硬件继承而来的,在大多数机器上,每个操作的部分不超过2**53。That’s more than adequate for most tasks, but you do need to keep in mind that it’s not decimal arithmetic and that every float operation can suffer a new rounding error.对于大多数任务来说,这已经足够了,但是你需要记住,这不是十进制算法,每个浮点运算都可能会遇到新的舍入误差。
While pathological cases do exist, for most casual use of floating-point arithmetic you’ll see the result you expect in the end if you simply round the display of your final results to the number of decimal digits you expect. 虽然病理病例确实存在,但对于大多数随意使用浮点运算的人来说,如果你只需将最终结果的显示四舍五入到你期望的小数位数,你最终就会看到你期望的结果。str()
usually suffices, and for finer control see the str.format()
method’s format specifiers in Format String Syntax.str()
通常就足够了,要获得更精细的控制,请参阅格式字符串语法中str.format()
方法的格式说明符。
For use cases which require exact decimal representation, try using the 对于需要精确十进制表示的用例,请尝试使用decimal
module which implements decimal arithmetic suitable for accounting applications and high-precision applications.decimal
模块,该模块实现适合会计应用和高精度应用的十进制算法。
Another form of exact arithmetic is supported by the fractions
module which implements arithmetic based on rational numbers (so the numbers like 1/3 can be represented exactly).fractions
模块支持另一种形式的精确算术,该模块实现基于有理数的算术(因此,像1/3这样的数字可以精确表示)。
If you are a heavy user of floating point operations you should take a look at the NumPy package and many other packages for mathematical and statistical operations supplied by the SciPy project. 如果你是一个大量使用浮点运算的人,你应该看看NumPy软件包以及SciPy项目提供的许多其他数学和统计运算软件包。See <https://scipy.org>.请参阅<https://scipy.org>。
Python provides tools that may help on those rare occasions when you really do want to know the exact value of a float. Python提供了一些工具,在您确实想知道浮点的确切值的罕见情况下可能会有所帮助。The float.as_integer_ratio()
method expresses the value of a float as a fraction:float.as_integer_ratio()
方法将浮点值表示为分数:
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
Since the ratio is exact, it can be used to losslessly recreate the original value:由于比率是精确的,因此可以使用它无损地重新创建原始值:
>>> x == 3537115888337719 / 1125899906842624
True
The float.hex()
method expresses a float in hexadecimal (base 16), again giving the exact value stored by your computer:float.hex()
方法用十六进制(以16为基数)表示浮点,同样给出计算机存储的精确值:
>>> x.hex()
'0x1.921f9f01b866ep+1'
This precise hexadecimal representation can be used to reconstruct the float value exactly:这种精确的十六进制表示法可用于精确重建浮点值:
>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True
Since the representation is exact, it is useful for reliably porting values across different versions of Python (platform independence) and exchanging data with other languages that support the same format (such as Java and C99).由于表示是精确的,因此它对于跨不同版本的Python可靠地移植值(平台独立性)以及与支持相同格式的其他语言(如Java和C99)交换数据非常有用。
Another helpful tool is the 另一个有用的工具是math.fsum()
function which helps mitigate loss-of-precision during summation. math.fsum()
函数,它有助于减少求和过程中的精度损失。It tracks “lost digits” as values are added onto a running total. 当数值加到一个总数值上时,它会跟踪“丢失的数字”。That can make a difference in overall accuracy so that the errors do not accumulate to the point where they affect the final total:这可能会影响整体精度,从而使误差不会累积到影响最终总精度的程度:
>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True
15.1. Representation Error表示错误¶
This section explains the “0.1” example in detail, and shows how you can perform an exact analysis of cases like this yourself. 本节详细解释了“0.1”示例,并展示了如何自己对此类案例进行精确分析。Basic familiarity with binary floating-point representation is assumed.假设对二进制浮点表示法基本熟悉。
Representation error refers to the fact that some (most, actually) decimal fractions cannot be represented exactly as binary (base 2) fractions. 表示错误指的是某些(实际上,大多数)十进制分数不能精确地表示为二进制(基数2)分数。This is the chief reason why Python (or Perl, C, C++, Java, Fortran, and many others) often won’t display the exact decimal number you expect.这就是为什么Python(或Perl、C、C++、Java、Fortran和许多其他语言)通常无法显示预期的精确十进制数的主要原因。
Why is that? 1/10 is not exactly representable as a binary fraction. 为什么?1/10不能精确地表示为二元分数。Almost all machines today (November 2000) use IEEE-754 floating point arithmetic, and almost all platforms map Python floats to IEEE-754 “double precision”. 今天(2000年11月)几乎所有的机器都使用IEEE-754浮点算法,几乎所有的平台都将Python浮点映射到IEEE-754“双精度”。754 doubles contain 53 bits of precision, so on input the computer strives to convert 0.1 to the closest fraction it can of the form J/2**N where J is an integer containing exactly 53 bits. 754 double包含53位精度,因此在输入时,计算机努力将0.1转换为最接近的分数,其形式为J/2**N
,其中J是一个正好包含53位的整数。Rewriting将
1 / 10 ~= J / (2**N)
as重写为
J ~= 2**N / 10
and recalling that J has exactly 53 bits (is 回想一下J正好有53位(>= 2**52
but < 2**53
), the best value for N is 56:>= 2**52
但小于< 2**53
),N的最佳值是56:
>>> 2**52 <= 2**56 // 10 < 2**53
True
That is, 56 is the only value for N that leaves J with exactly 53 bits. 也就是说,56是N的唯一一个值,它使J正好有53位。The best possible value for J is then that quotient rounded:J的最佳可能值是四舍五入的商:
>>> q, r = divmod(2**56, 10)
>>> r
6
Since the remainder is more than half of 10, the best approximation is obtained by rounding up:由于余数大于10的一半,因此通过四舍五入获得最佳近似值:
>>> q+1
7205759403792794
Therefore the best possible approximation to 1/10 in 754 double precision is:因此,754双精度中1/10的最佳近似值为:
7205759403792794 / 2 ** 56
Dividing both the numerator and denominator by two reduces the fraction to:将分子和分母除以2,则分数为:
3602879701896397 / 2 ** 55
Note that since we rounded up, this is actually a little bit larger than 1/10; if we had not rounded up, the quotient would have been a little bit smaller than 1/10. 注意,由于我们四舍五入,这实际上比1/10大一点;如果我们没有四舍五入,商数会比1/10小一点。But in no case can it be exactly 1/10!但无论如何都不能精确到1/10!
So the computer never “sees” 1/10: what it sees is the exact fraction given above, the best 754 double approximation it can get:所以计算机永远不会“看到”1/10:它看到的是上面给出的精确分数,它能得到的最佳754双近似值:
>>> 0.1 * 2 ** 55
3602879701896397.0
If we multiply that fraction by 10**55, we can see the value out to 55 decimal digits:如果我们将该分数乘以10**55
,我们可以看到55位小数:
>>> 3602879701896397 * 10 ** 55 // 2 ** 55
1000000000000000055511151231257827021181583404541015625
meaning that the exact number stored in the computer is equal to the decimal value 0.1000000000000000055511151231257827021181583404541015625. 这意味着存储在计算机中的确切数字等于十进制值0.1000000000000005555115123125782702118158340451015625
。Instead of displaying the full decimal value, many languages (including older versions of Python), round the result to 17 significant digits:许多语言(包括较早版本的Python)不显示完整的十进制值,而是将结果四舍五入到17位有效数字:
>>> format(0.1, '.17f')
'0.10000000000000001'
The fractions
and decimal
modules make these calculations easy:fractions
和decimal
模块使这些计算变得简单:
>>> from decimal import Decimal
>>> from fractions import Fraction
>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> format(Decimal.from_float(0.1), '.17')
'0.10000000000000001'