简体   繁体   English

为什么浮点值 4*0.1 在 Python 3 中看起来不错,而 3*0.1 则不然?

[英]Why does the floating-point value of 4*0.1 look nice in Python 3 but 3*0.1 doesn't?

I know that most decimals don't have an exact floating point representation ( Is floating point math broken? ).我知道大多数小数没有精确的浮点表示(浮点数学是否被破坏? )。

But I don't see why 4*0.1 is printed nicely as 0.4 , but 3*0.1 isn't, when both values actually have ugly decimal representations:但我不明白为什么4*0.1被很好地打印为0.4 ,但3*0.1不是,因为这两个值实际上都有丑陋的十进制表示:

>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')

The simple answer is because 3*0.1 != 0.3 due to quantization (roundoff) error (whereas 4*0.1 == 0.4 because multiplying by a power of two is usually an "exact" operation).简单的答案是因为量化(舍入)错误导致3*0.1 != 0.3 (而4*0.1 == 0.4因为乘以 2 的幂通常是“精确”运算)。 Python tries to find the shortest string that would round to the desired value , so it can display 4*0.1 as 0.4 as these are equal, but it cannot display 3*0.1 as 0.3 because these are not equal. Python 尝试找到四舍五入到所需值的最短字符串,因此它可以将4*0.1显示为0.4因为它们是相等的,但它不能将3*0.1显示为0.3因为它们不相等。

You can use the .hex method in Python to view the internal representation of a number (basically, the exact binary floating point value, rather than the base-10 approximation).您可以使用 Python 中的.hex方法查看数字的内部表示(基本上,确切的二进制浮点值,而不是以 10 为底的近似值)。 This can help to explain what's going on under the hood.这有助于解释幕后发生的事情。

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'

0.1 is 0x1.999999999999a times 2^-4. 0.1 是 0x1.999999999999a 乘以 2^-4。 The "a" at the end means the digit 10 - in other words, 0.1 in binary floating point is very slightly larger than the "exact" value of 0.1 (because the final 0x0.99 is rounded up to 0x0.a).在“a”结尾手段数字10 -换句话说,在二进制浮点0.1比“精确”的0.1的值(因为最后0x0.99被向上舍入到0x0.a)非常稍大 When you multiply this by 4, a power of two, the exponent shifts up (from 2^-4 to 2^-2) but the number is otherwise unchanged, so 4*0.1 == 0.4 .当您将其乘以 4(2 的幂)时,指数会向上移动(从 2^-4 到 2^-2),但数字不变,因此4*0.1 == 0.4

However, when you multiply by 3, the tiny little difference between 0x0.99 and 0x0.a0 (0x0.07) magnifies into a 0x0.15 error, which shows up as a one-digit error in the last position.但是,当您乘以 3 时,0x0.99 和 0x0.a0 (0x0.07) 之间的微小差异会放大为 0x0.15 错误,这在最后一个位置显示为一位数错误。 This causes 0.1*3 to be very slightly larger than the rounded value of 0.3.这会导致 0.1*3 比四舍五入的值 0.3略大

Python 3's float repr is designed to be round-trippable , that is, the value shown should be exactly convertible into the original value ( float(repr(f)) == f for all floats f ). Python 3 的 float repr被设计为可往返的,也就是说,显示的值应该可以完全转换为原始值( float(repr(f)) == f对于所有浮点f )。 Therefore, it cannot display 0.3 and 0.1*3 exactly the same way, or the two different numbers would end up the same after round-tripping.因此,它不能以完全相同的方式显示0.30.1*3 ,否则两个不同的数字在往返后最终会相同。 Consequently, Python 3's repr engine chooses to display one with a slight apparent error.因此,Python 3 的repr引擎选择显示一个带有轻微明显错误的内容。

repr (and str in Python 3) will put out as many digits as required to make the value unambiguous. repr (和 Python 3 中的str )将根据需要输出尽可能多的数字,以使值明确。 In this case the result of the multiplication 3*0.1 isn't the closest value to 0.3 (0x1.3333333333333p-2 in hex), it's actually one LSB higher (0x1.3333333333334p-2) so it needs more digits to distinguish it from 0.3.在这种情况下,乘法3*0.1的结果不是最接近 0.3 的值(十六进制为 0x1.3333333333333p-2),它实际上高一个 LSB(0x1.33333333333334p-2),因此它需要更多的数字来区分0.3.

On the other hand, the multiplication 4*0.1 does get the closest value to 0.4 (0x1.999999999999ap-2 in hex), so it doesn't need any additional digits.另一方面,乘法4*0.1确实得到最接近 0.4 的值(十六进制为 0x1.999999999999ap-2),因此它不需要任何额外的数字。

You can verify this quite easily:您可以很容易地验证这一点:

>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True

I used hex notation above because it's nice and compact and shows the bit difference between the two values.我在上面使用了十六进制表示法,因为它既美观又紧凑,并显示了两个值之间的位差异。 You can do this yourself using eg (3*0.1).hex() .您可以使用例如(3*0.1).hex()自己完成此操作。 If you'd rather see them in all their decimal glory, here you go:如果你更愿意看到他们十进制的荣耀,那么你去吧:

>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')

Here's a simplified conclusion from other answers.这是其他答案的简化结论。

If you check a float on Python's command line or print it, it goes through function repr which creates its string representation.如果您在 Python 的命令行上检查浮点数或打印它,它会通过函数repr来创建其字符串表示。

Starting with version 3.2, Python's str and repr use a complex rounding scheme, which prefers nice-looking decimals if possible, but uses more digits where necessary to guarantee bijective (one-to-one) mapping between floats and their string representations.从版本 3.2 开始,Python 的strrepr使用复杂的舍入方案,如果可能的话,它更喜欢漂亮的小数,但在必要时使用更多的数字来保证浮点数和它们的字符串表示之间的双射(一对一)映射。

This scheme guarantees that value of repr(float(s)) looks nice for simple decimals, even if they can't be represented precisely as floats (eg. when s = "0.1") .该方案保证repr(float(s))值对于简单小数看起来不错,即使它们不能精确地表示为浮点数(例如,当s = "0.1")

At the same time it guarantees that float(repr(x)) == x holds for every float x同时它保证float(repr(x)) == x对每个浮点x

Not really specific to Python's implementation but should apply to any float to decimal string functions.并非真正特定于 Python 的实现,但应适用于任何浮点数到十进制字符串函数。

A floating point number is essentially a binary number, but in scientific notation with a fixed limit of significant figures.浮点数本质上是一个二进制数,但采用科学计数法,有效数字有固定限制。

The inverse of any number that has a prime number factor that is not shared with the base will always result in a recurring dot point representation.具有不与基数共享的素数因子的任何数的倒数总是会导致重复出现的点点表示。 For example 1/7 has a prime factor, 7, that is not shared with 10, and therefore has a recurring decimal representation, and the same is true for 1/10 with prime factors 2 and 5, the latter not being shared with 2;例如 1/7 有一个质因数 7,它不与 10 共享,因此具有循环的十进制表示,对于质因数为 2 和 5 的 1/10 也是如此,后者不与 2 共享; this means that 0.1 cannot be exactly represented by a finite number of bits after the dot point.这意味着 0.1 不能由点点后的有限位数精确表示。

Since 0.1 has no exact representation, a function that converts the approximation to a decimal point string will usually try to approximate certain values so that they don't get unintuitive results like 0.1000000000004121.由于 0.1 没有精确表示,因此将近似值转换为小数点字符串的函数通常会尝试近似某些值,以便它们不会得到像 0.1000000000004121 这样的不直观的结果。

Since the floating point is in scientific notation, any multiplication by a power of the base only affects the exponent part of the number.由于浮点数采用科学记数法,因此任何乘以基数的幂只会影响数字的指数部分。 For example 1.231e+2 * 100 = 1.231e+4 for decimal notation, and likewise, 1.00101010e11 * 100 = 1.00101010e101 in binary notation.例如 1.231e+2 * 100 = 1.231e+4 十进制表示法,同样, 1.00101010e11 * 100 = 1.00101010e101 二进制表示法。 If I multiply by a non-power of the base, the significant digits will also be affected.如果我乘以基数的非幂,则有效数字也会受到影响。 For example 1.2e1 * 3 = 3.6e1例如 1.2e1 * 3 = 3.6e1

Depending on the algorithm used, it may try to guess common decimals based on the significant figures only.根据所使用的算法,它可能会尝试仅根据有效数字来猜测常见的小数。 Both 0.1 and 0.4 have the same significant figures in binary, because their floats are essentially truncations of (8/5) (2^-4) and (8/5) (2^-6) respectively. 0.1 和 0.4 的二进制有效数字相同,因为它们的浮点数本质上分别是 (8/5) (2^-4) 和 (8/5) (2^-6) 的截断。 If the algorithm identifies the 8/5 sigfig pattern as the decimal 1.6, then it will work on 0.1, 0.2, 0.4, 0.8, etc. It may also have magic sigfig patterns for other combinations, such as the float 3 divided by float 10 and other magic patterns statistically likely to be formed by division by 10.如果算法将 8/5 sigfig 模式识别为十进制 1.6,那么它将适用于 0.1、0.2、0.4、0.8 等。它也可能有其他组合的魔术 sigfig 模式,例如浮点 3 除以浮点 10和其他在统计上很可能通过除以 10 形成的魔法模式。

In the case of 3*0.1, the last few significant figures will likely be different from dividing a float 3 by float 10, causing the algorithm to fail to recognize the magic number for the 0.3 constant depending on its tolerance for precision loss.在 3*0.1 的情况下,最后几个有效数字可能不同于浮点数 3 除以浮点数 10,导致算法无法识别 0.3 常量的幻数,具体取决于其对精度损失的容忍度。

Edit: https://docs.python.org/3.1/tutorial/floatingpoint.html编辑: https : //docs.python.org/3.1/tutorial/floatingpoint.html

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. 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.例如,数字0.1和0.10000000000000001和0.1000000000000000055511151231257827021181583404541015625都由2分之3602879701896397** 55.近似由于所有这些十进制值的共享相同的近似,可以同时仍保持不变的eval(再版(X来显示它们中的任何一个) ) == x。

There is no tolerance for precision loss, if float x (0.3) is not exactly equal to float y (0.1*3), then repr(x) is not exactly equal to repr(y).精度损失没有容忍度,如果 float x (0.3) 不完全等于 float y (0.1*3),则 repr(x) 不完全等于 repr(y)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么在Python中此表达式“ 1 == 1和0或0.1”的值等于0.1? - Why the value of this expression “1 == 1 and 0 or 0.1” equals 0.1 in python? 为什么在 python 中允许浮点切片 (slice(0,1,0.1)),但调用索引方法 (slice(0,1,0.1).indices) 会引发 TypeError? - Why is floating point slicing (slice(0,1,0.1)) allowed in python, but calling the indices method (slice(0,1,0.1).indices) raises TypeError? python中的极端浮点精度数1.0-(0.1^200) - Extreme floating point precision number 1.0-(0.1^200) in python 为什么舍入浮点数1.4999999999999999产生2? - Why does rounding the floating-point number 1.4999999999999999 produce 2? 为什么复杂的浮点除法 NumPy 会奇怪地下溢? - Why does complex floating-point division underflow weirdly with NumPy? 为什么Python中的浮点除法用较小的数字更快? - Why is floating-point division in Python faster with smaller numbers? 我正在学习Python,为什么在此示例中使用浮点数? - I'm learning Python and why is a floating-point used in this example? 为什么 1.1-1 给出近似值但 1/10 在 python 3 中给出 0.1,当它们都表示相同的值时 - Why 1.1-1 gives approx value but 1/10 gives 0.1 in python 3, when both of them represent the same value 为什么在浮点算术中0.9999999999999999!= 1但0.9999999999999999 + 1 == 2 - Why, in floating-point arithmetic, 0.9999999999999999 !=1 but 0.9999999999999999 + 1 == 2 在 Python3 中处理浮点数 - Handling floating-point numbers in Python3
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM