PDi*_*lta 2 python floating-point numpy rounding-error rounding
我正在使用浮点数.如果我做:
import numpy as np
np.round(100.045, 2)
Run Code Online (Sandbox Code Playgroud)
我明白了:
Out[15]: 100.04
Run Code Online (Sandbox Code Playgroud)
显然,这应该是100.05.我知道IEEE 754的存在,并且浮点数的存储方式是导致这种舍入误差的原因.
我的问题是:我怎样才能避免这个错误?
你是部分正确的,通常这种"不正确的舍入"的原因是由于存储浮点数的方式.一些浮点文字可以完全表示为浮点数,而其他浮点文字则不能.
>>> a = 100.045
>>> a.as_integer_ratio() # not exact
(7040041011254395, 70368744177664)
>>> a = 0.25
>>> a.as_integer_ratio() # exact
(1, 4)
Run Code Online (Sandbox Code Playgroud)
同样重要的是要知道无法100.045从生成的浮点数恢复您使用的文字().所以你唯一能做的就是使用任意精度数据类型而不是文字.例如,您可以使用Fraction或Decimal(仅提及两种内置类型).
我提到的,你不能恢复字面一旦被解析为浮动-所以你必须输入它作为字符串或其他什么东西,代表数字准确,并通过这些数据类型的支持:
>>> from fractions import Fraction
>>> f = Fraction(100045, 100)
>>> f
Fraction(20009, 20)
>>> f = Fraction("100.045")
>>> f
Fraction(20009, 20)
>>> from decimal import Decimal
>>> Decimal("100.045")
Decimal('100.045')
Run Code Online (Sandbox Code Playgroud)
然而,这些与NumPy不兼容,即使你完全可以工作 - 与基本的浮点运算相比,它几乎肯定会非常慢.
>>> import numpy as np
>>> a = np.array([Decimal("100.045") for _ in range(1000)])
>>> np.round(a)
AttributeError: 'decimal.Decimal' object has no attribute 'rint'
Run Code Online (Sandbox Code Playgroud)
一开始我说你只是部分正确.还有另一个转折!
你提到四舍五入100.045 显然会给100.05.但这根本不明显,在你的情况下甚至是错误的(在编程浮点数学的情况下 - 对于"正常计算"来说也是如此).在许多编程语言中,"一半"值(小数点后四舍五入的数字为5)并不总是四舍五入 - 例如Python(和NumPy)使用"舍入一半甚至"方法,因为它的偏差较小.例如,0.5将四舍五入到0while 1.5将四舍五入到2.
因此,即使100.045可以完全表示为浮点数 - 它仍然会100.04因为舍入规则而转向!
>>> round(Fraction("100.045"), 1)
Fraction(5002, 5)
>>> 5002 / 5
1000.4
>>> d = Decimal("100.045")
>>> round(d, 2)
Decimal('100.04')
Run Code Online (Sandbox Code Playgroud)
这在NumPy文档中甚至提到numpy.around:
笔记
对于正好在舍入小数值之间的值,NumPy舍入到最接近的偶数值.因此,由于IEEE浮点标准[R1011]中的小数部分的不精确表示以及当以10的幂进行缩放时引入的误差,结果也可能是令人惊讶的1.5和2.5轮到2.0,-0.5和0.5轮到0.0等.
(强调我的.)
Python中允许手动设置舍入规则的唯一(至少我知道)数字类型是Decimal- via ROUND_HALF_UP:
>>> from decimal import Decimal, getcontext, ROUND_HALF_UP
>>> dc = getcontext()
>>> dc.rounding = ROUND_HALF_UP
>>> d = Decimal("100.045")
>>> round(d, 2)
Decimal('100.05')
Run Code Online (Sandbox Code Playgroud)
所以为了避免"错误"你必须:
基本上,这个问题没有通用的解决方案,除非您对所有不同情况都有通用规则(请参阅浮点算术:问题和限制)。但是,在这种情况下,您可以单独对小数部分进行四舍五入:
In [24]: dec, integ = np.modf(100.045)
In [25]: integ + np.round(dec, 2)
Out[25]: 100.05
Run Code Online (Sandbox Code Playgroud)
这种行为的原因并不是因为将整数与小数部分分开会对round()的逻辑产生任何影响。这是因为当您使用fmod它时,您会获得更真实的数字小数部分,这实际上是四舍五入的表示形式。
在本例中,情况如下dec:
In [30]: dec
Out[30]: 0.045000000000001705
Run Code Online (Sandbox Code Playgroud)
您可以使用以下命令检查该轮给出的结果是否相同0.045:
In [31]: round(0.045, 2)
Out[31]: 0.04
Run Code Online (Sandbox Code Playgroud)
现在,如果您尝试使用另一个数字,例如100.0333,小数部分是一个稍小的版本,正如我提到的,您想要的结果取决于您的舍入策略。
In [37]: dec, i = np.modf(100.0333)
In [38]: dec
Out[38]: 0.033299999999997
Run Code Online (Sandbox Code Playgroud)
还有像fractions和decimal这样的模块提供对快速正确舍入的十进制浮点和有理算术的支持,您可以在此类情况下使用它们。