代码
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两个。
答案 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 强>
内置round
和numpy.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结束'。我不确定这个算法是好还是坏,但绝对不太适合手动检查。