Python地板除法中的舍入错误 [英] rounding errors in Python floor division

查看:34
本文介绍了Python地板除法中的舍入错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道在浮点运算中会发生舍入错误,但有人可以解释一下原因:

<预><代码>>>>8.0/0.4 # 符合预期20.0>>>floor(8.0/0.4) # int 也可以20>>>8.0//0.4 # 期待 20.019.0

这在 x64 上的 Python 2 和 3 上都会发生.

在我看来,这要么是一个错误,要么是 // 的一个非常愚蠢的规范,因为我看不出为什么最后一个表达式应该计算为 19.0.

为什么不将 a//b 简单地定义为 floor(a/b) ?

EDIT:8.0 % 0.4 也计算为 0.39999999999999996.至少从那以后 8.0//0.4 * 0.4 + 8.0 % 0.4 计算结果为 8.0

编辑:这不是浮点数学被破坏了吗?的副本,因为我在问为什么这个特定操作会出现(也许可以避免)舍入错误,以及为什么 a//b 没有定义为/等于 floor(a/b)

备注:我想这行不通的更深层原因是楼层划分是不连续的,因此具有无限的条件编号 使它成为一个不适定的问题.楼层除法和浮点数从根本上是不兼容的,你永远不应该在浮点数上使用 //.只需使用整数或分数即可.

解决方案

正如您和 khelwood 已经注意到的,0.4 不能完全表示为浮点数.为什么?它是没有有限二进制分数表示的五分之二 (4/10 == 2/5).

试试这个:

from fractions import Fraction分数('8.0')//分数('0.4')# 或等效# 分数(8, 1)//分数(2, 5)# 或者# 分数('8/1')//分数('2/5')#20

不过

Fraction('8')//分数(0.4)# 19

此处,0.4 被解释为需要(二进制)四舍五入的浮点文字(因此是浮点二进制数),并且只有 then 转换为有理数Fraction(3602879701896397, 9007199254740992),几乎但不完全是 4/10.然后执行地板除法,因为

19 * 分数(3602879701896397, 9007199254740992) <8.0

20 * 分数(3602879701896397, 9007199254740992) >8.0

结果是 19,而不是 20.

同样的情况可能发生在

8.0//0.4

即,似乎地板除法是原子确定的(但在解释的浮点文字的唯一近似浮点值上).

为什么会这样

楼层(8.0/0.4)

给出正确"的结果?因为在那里,两个舍入误差相互抵消.首先 1) 执行除法,产生略小于 20.0 的值,但不能表示为浮点数.它四舍五入到最接近的浮点数,恰好是 20.0.只有然后floor操作被执行,但是现在作用在exactly 20.0,因此不会改变任何数字更多.

<小时>

1) 饰演 Kyle Strand 指出,确切的结果是确定的然后四舍五入不是实际上发生的低2)-level(CPython 的 C 代码甚至 CPU 指令).但是,它可以成为确定预期 3) 结果的有用模型.

2) 然而,在最低 4) 级别,这可能不会太远.一些芯片组通过首先计算更精确(但仍然不精确,只是有更多二进制数字)的内部浮点结果,然后四舍五入到 IEEE 双精度来确定浮点结果.

3) Python 规范预期",不一定是我们的直觉.

4) 那么,最低层上面逻辑门.我们不必考虑使半导体能够理解这一点的量子力学.

I know rounding errors happen in floating point arithmetic but can somebody explain the reason for this one:

>>> 8.0 / 0.4  # as expected
20.0
>>> floor(8.0 / 0.4)  # int works too
20
>>> 8.0 // 0.4  # expecting 20.0
19.0

This happens on both Python 2 and 3 on x64.

As far as I see it this is either a bug or a very dumb specification of // since I don't see any reason why the last expression should evaluate to 19.0.

Why isn't a // b simply defined as floor(a / b) ?

EDIT: 8.0 % 0.4 also evaluates to 0.3999999999999996. At least this is consequent since then 8.0 // 0.4 * 0.4 + 8.0 % 0.4 evaluates to 8.0

EDIT: This is not a duplicate of Is floating point math broken? since I am asking why this specific operation is subject to (maybe avoidable) rounding errors, and why a // b isn't defined as / equal to floor(a / b)

REMARK: I guess that the deeper reason why this doesn't work is that floor division is discontinuous and thus has an infinite condition number making it an ill-posed problem. Floor division and floating-point numbers simply are fundamentally incompatible and you should never use // on floats. Just use integers or fractions instead.

解决方案

As you and khelwood already noticed, 0.4 cannot be exactly represented as a float. Why? It is two fifth (4/10 == 2/5) which does not have a finite binary fraction representation.

Try this:

from fractions import Fraction
Fraction('8.0') // Fraction('0.4')
    # or equivalently
    #     Fraction(8, 1) // Fraction(2, 5)
    # or
    #     Fraction('8/1') // Fraction('2/5')
# 20

However

Fraction('8') // Fraction(0.4)
# 19

Here, 0.4 is interpreted as a float literal (and thus a floating point binary number) which requires (binary) rounding, and only then converted to the rational number Fraction(3602879701896397, 9007199254740992), which is almost but not exactly 4 / 10. Then the floored division is executed, and because

19 * Fraction(3602879701896397, 9007199254740992) < 8.0

and

20 * Fraction(3602879701896397, 9007199254740992) > 8.0

the result is 19, not 20.

The same probably happens for

8.0 // 0.4

I.e., it seems floored division is determined atomically (but on the only approximate float values of the interpreted float literals).

So why does

floor(8.0 / 0.4)

give the "right" result? Because there, two rounding errors cancel each other out. First 1) the division is performed, yielding something slightly smaller than 20.0, but not representable as float. It gets rounded to the closest float, which happens to be 20.0. Only then, the floor operation is performed, but now acting on exactly 20.0, thus not changing the number any more.


1) As Kyle Strand points out, that the exact result is determined then rounded isn't what actually happens low2)-level (CPython's C code or even CPU instructions). However, it can be a useful model for determining the expected 3) result.

2) On the lowest 4) level, however, this might not be too far off. Some chipsets determine float results by first computing a more precise (but still not exact, simply has some more binary digits) internal floating point result and then rounding to IEEE double precision.

3) "expected" by the Python specification, not necessarily by our intuition.

4) Well, lowest level above logic gates. We don't have to consider the quantum mechanics that make semiconductors possible to understand this.

这篇关于Python地板除法中的舍入错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆