Numpy以不同于python的方式进行回合

时间:2018-02-06 08:19:46

标签: python numpy rounding-error rounding

代码

import numpy as np
a = 5.92270987499999979065
print(round(a, 8))
print(round(np.float64(a), 8))

给出

5.92270987
5.92270988

知道为什么吗?

在numpy来源中找不到任何相关内容。

更新
我知道处理这个问题的正确方法是以这种差异无关紧要的方式构建程序。我做的。我在回归测试中偶然发现了它。

UPDATE2:
关于@VikasDamodar评论。一个人不应该信任repr()函数:

>>> np.float64(5.92270987499999979065)
5.922709875
>>> '%.20f' % np.float64(5.92270987499999979065)
'5.92270987499999979065'

UPDATE3:
测试了python3.6.0 x32,numpy 1.14.0,win64。另外在python3.6.4 x64,numpy 1.14.0,debian。

UPDATE4:
只是为了确定:

import numpy as np
a = 5.92270987499999979065
print('%.20f' % round(a, 8))
print('%.20f' % round(np.float64(a), 8))

5.92270987000000026512
5.92270988000000020435

Update5:
以下代码演示了在不使用str的情况下发生差异的阶段:

>>> np.float64(a) - 5.922709874
1.000000082740371e-09
>>> a - 5.922709874
1.000000082740371e-09
>>> round(np.float64(a), 8) - 5.922709874
6.000000496442226e-09
>>> round(a, 8) - 5.922709874
-3.999999442783064e-09

显然,在应用' round'之前他们是相同的数字。

Update6:
与@ user2357112的回答相比,np.round大约比回合慢4倍:

%%timeit a = 5.92270987499999979065
round(a, 8)

1.18 µs ± 26.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  

%%timeit a = np.float64(5.92270987499999979065)
round(a, 8)

4.05 µs ± 43.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

另外在我看来,np.round做得更好,甚至比内置round更接近:最初我得到这个5.92270987499999979065数字,除以11.84541975两个。

4 个答案:

答案 0 :(得分:4)

float.__round__需要特别注意使用正确舍入的双字符串算法生成正确的舍入结果。

NumPy没有。 NumPy docs提到

  

结果也可能令人惊讶,因为IEEE浮点标准[R9]中的小数部分表示不精确,而以10的幂为单位引入的错误。

这样更快,但会产生更多的舍入误差。它会导致像您观察到的错误,以及错误,其中数字甚至更明确地低于截止值仍然会被四舍五入:

>>> x = 0.33499999999999996
>>> x
0.33499999999999996
>>> x < 0.335
True
>>> x < Decimal('0.335')
True
>>> x < 0.67/2
True
>>> round(x, 2)
0.33
>>> numpy.round(x, 2)
0.34000000000000002

你得到的NumPy舍入时间较慢,但这与舍入算法较慢无关。 NumPy和常规Python数学之间的任何时间比较都归结为NumPy针对整个阵列操作进行了优化。在单个NumPy标量上进行数学计算有很多开销,但使用numpy.round对整个数组进行舍入很容易使用round对浮点数进行舍入:

In [6]: import numpy

In [7]: l = [i/7 for i in range(100)]

In [8]: a = numpy.array(l)

In [9]: %timeit [round(x, 1) for x in l]
59.6 µs ± 408 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [10]: %timeit numpy.round(a, 1)
5.27 µs ± 145 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

至于哪一个更准确,那肯定是float.__round__。你的号码更接近于5.92270987而不是5.92270988,并且它是圆形的 tie -to-even,而不是round-everything-to-even。这里没有关系。

答案 1 :(得分:0)

是的,另一种处理这类事情的方法是在python3中使用不是 慢的Decimal:

%%timeit d = D('11.84541975'); q = D('0.00000001')
(d/2).quantize(q)

485 ns ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

答案 2 :(得分:-1)

<强> tldr

内置roundnumpy.round使用不同的舍入算法。对于大多数数字,它们的结果是相同的,但对于某些角落情况则大不相同

两者都适合某些用途。

round对于标量来说速度更快,np.round对于数组更快。

<强>解释

•内置round使用直接检查请求后的十进制数字的方法。无论发生什么事情(即使它是...... 499999),它向下四舍五入,并且将5舍入到最近的偶数,除非之后有一个1(例如...... 500001),在这种情况下它会向上舍入。

np.round将数字乘以所要求的10的幂,通过普通规则舍入到最接近的int,然后再乘以10的相同幂。

它为0.33 / 2的案例提供了更可预测的结果:

>>> 0.33/2
0.165
>>> round(0.33/2, 2)
0.17
>>> np.round(0.33/2, 2)
0.16

这里0.165应该四舍五入到最近的偶数,即0.16。

<强>更新
然而,对于像1.09 / 2这样的案件,它遭遇了四舍五入的错误(正如Mark Dickinson在评论中所指出的那样):

>>> 1.09/2
0.545
>>> round(1.09/2, 2)
0.55
>>> np.round(1.09/2, 2)
0.55

我能想到的唯一解决方法是

>>> round(round(1.09*100)/2)/100
0.54

有效,但远非普遍。

答案 3 :(得分:-2)

在我的特殊情况下,通过乘法和除法来解决这两个函数之间差异以获得一致结果的非常直接的方法。

对于我的应用程序,它似乎比本机round效果更好,结果与np.round相同:

'%.20f' % (round(a*1e8)/1e8)
'5.92270988000000020435'
'%.20f' % (round(np.float64(a)*1e8)/1e8)
'5.92270988000000020435'

<强>更新
感谢@ user2357112我发现它正是np.round内部发生的事情(multiarray/calculation.c#L665),所以除非你在numpy和native python之间交叉测试你的结果,否则使用numpy是安全的圆形版本没有那些额外的分区和python级别的乘法。

<强> UPDATE2
在处理标量时,这种在python级别上的除法和乘法方法比原始round慢一些(~30%),但比np.round明显更快(~3次)(给出与{相同的结果) {1}}):

np.round

<强> UPDATE3
Python的内置%%timeit c = 11.84541975 round(c/2) 349 ns ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %%timeit c = 11.84541975 round(c*1e8/2)/1e8 519 ns ± 13 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %%timeit c = np.float64(11.84541975) round(c/2) 1.67 µs ± 20.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) %%timeit c = np.float64(11.84541975) round(c*1e8/2)/1e8 2.01 µs ± 37.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) 使用了一种简单的方法,它只对完全可表示的二元有理数(如0.375(整数除以2的精确幂)进行“舍入到偶数”规则),因此有效地用此规则代替所有其他数字的'围绕领带数,恰好有49999表示下来,恰好以50001结束'。我不确定这个算法是好还是坏,但绝对不太适合手动检查。