我正在使用浮点数。如果我这样做:
import numpy as np
np.round(100.045, 2)
我明白了:
Out[15]: 100.04
显然,这应该是100.05
。我知道IEEE 754的存在,并且浮点数的存储方式是导致这种舍入误差的原因。
我的问题是:我怎样避免这个错误?
答案 0 :(得分:7)
你是部分正确的,通常这种“不正确的舍入”的原因是由于存储浮点数的方式。一些浮点文字可以完全表示为浮点数,而其他浮点文字则不能。
>>> a = 100.045
>>> a.as_integer_ratio() # not exact
(7040041011254395, 70368744177664)
>>> a = 0.25
>>> a.as_integer_ratio() # exact
(1, 4)
了解您无法从生成的浮点数恢复您使用的文字(100.045
)也很重要。所以你唯一能做的就是使用任意精度数据类型而不是文字。例如,您可以使用Fraction
或Decimal
(仅提及两种内置类型)。
我提到一旦将其解析为float,就无法恢复文字 - 因此您必须将其作为字符串或其他代表完全的数字输入,并且受这些数据类型的支持: / p>
>>> 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')
然而,这些与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'
一开始我说你只是部分正确。还有另一个转折!
你提到四舍五入100.045 显然给100.05。但这根本不明显,在你的情况下甚至是错误的(在编程浮点数学的情况下 - 对于“正常计算”来说也是如此)。在许多编程语言中,“half”值(小数点后的数字为5)不会总是向上舍入 - 例如Python(和NumPy)使用{{3}因为它偏向性较小。例如,0.5
将四舍五入为0
,而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')
在"round half to even" approach的NumPy文档中甚至提到了这一点:
注释
对于正好在舍入小数值之间的值,NumPy会舍入到最接近的偶数值。因此,由于IEEE浮点标准[R1011]中的小数部分的不精确表示以及当以10的幂进行缩放时引入的误差,结果也可能是令人惊讶的1.5和2.5轮到2.0,-0.5和0.5轮到0.0等。
(强调我的。)
Python中允许手动设置舍入规则的唯一(至少我知道)数字类型是Decimal
- 来自numpy.around
:
>>> 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')
所以为了避免“错误”,你必须:
答案 1 :(得分:1)
基本上没有针对此问题的一般解决方案IMO,除非您对所有差异情况都有一般规则(参见Floating Point Arithmetic: Issues and Limitation)。但是,在这种情况下,您可以单独舍入小数部分:
In [24]: dec, integ = np.modf(100.045)
In [25]: integ + np.round(dec, 2)
Out[25]: 100.05
这种行为的原因并不是因为从小数部分中分离整数会对round()
逻辑产生任何影响。这是因为当你使用fmod
时,它会为你提供一个更真实的数字小数部分版本,实际上是一个圆角表示。
在这种情况下,dec
是:
In [30]: dec
Out[30]: 0.045000000000001705
您可以检查该轮次与0.045
:
In [31]: round(0.045, 2)
Out[31]: 0.04
现在,如果您尝试使用100.0333
之类的其他数字,小数部分是一个稍小的版本,正如我所提到的,您想要的结果取决于您的舍入政策。
In [37]: dec, i = np.modf(100.0333)
In [38]: dec
Out[38]: 0.033299999999997
答案 2 :(得分:0)
这不是错误,而是功能)))
您可以简单地使用此技巧:
def myround(val):
"Fix pythons round"
d,v = math.modf(val)
if d==0.5:
val += 0.000000001
return round(val)