如何使用numpy.round避免不正确的舍入?

时间:2018-05-16 15:25:36

标签: python numpy floating-point rounding rounding-error

我正在使用浮点数。如果我这样做:

import numpy as np
np.round(100.045, 2)

我明白了:

Out[15]: 100.04

显然,这应该是100.05。我知道IEEE 754的存在,并且浮点数的存储方式是导致这种舍入误差的原因。

我的问题是:我怎样避免这个错误?

3 个答案:

答案 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)也很重要。所以你唯一能做的就是使用任意精度数据类型而不是文字。例如,您可以使用FractionDecimal(仅提及两种内置类型)。

我提到一旦将其解析为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')

摘要

所以为了避免“错误”,你必须:

  • 阻止Python将其解析为浮点值和
  • 使用可以完全代表它的数据类型
  • 然后你必须手动覆盖默认的舍入模式,这样你才能完成“一半”。
  • (放弃NumPy,因为它没有任意精度数据类型)

答案 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

还有像fractionsdecimal这样的模块,它们支持快速正确舍入的十进制浮点和有理算术,可以在这种情况下使用。

答案 2 :(得分:0)

这不是错误,而是功能)))

您可以简单地使用此技巧:

def myround(val):
"Fix pythons round"
d,v = math.modf(val)
if d==0.5:
    val += 0.000000001
return round(val)