代码
1 2 3 4
| import numpy as np
a = 5.92270987499999979065
print(round(a, 8))
print(round(np.float64(a), 8)) |
给
知道为什么吗?
在numpy来源中找不到任何相关内容。
更新:
我知道处理这个问题的正确方法是以这种差异无关紧要的方式构建程序。我做的。我在回归测试中偶然发现了它。
UPDATE2:
关于@VikasDamodar评论。一个人不应该相信repr()函数:
1 2 3 4
| >>> 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:
只是要确定:
1 2 3 4 5 6 7
| 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的情况下在哪个阶段发生差异:
1 2 3 4 5 6 7 8
| >>> 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倍:
1 2 3 4 5 6 7 8 9
| %%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两个。
-
PS我知道round(0.5)==0和round(1.5)==2。
-
哇。确实round(0.5) == 0。这是为什么?这似乎是一个严重的错误!
-
@mrCarnivore round(0.5)==0是大多数计算机语言的设计。 en.wikipedia.org/wiki/Rounding
-
@mrCarnivore它是圆形到最近的偶数...不是一个bug ..但仍然很奇怪..
-
然后,自从大学以来,我对数字的知识已经消失了......我不知道到最接近的甚至是设计的四舍五入。很高兴知道!谢谢。
-
有趣的是,对于第9位数字,它们围绕着相同的......这似乎在于numpys round的舍入策略(np.round(a,8)也会向上舍入到8)我猜?
-
np.float64(a)的问题将返回5.922709875,因此当np对数字进行舍入时,它会给出正确的o / p。
-
@VikasDamodar见上面的update2
-
哪个Python arch / version / OS?它在Python 2.7.14 win32中给出了相同的结果。
-
@AntonyHatchkins不再是数字了,它变成了str
-
@VikasDamodar。它不仅变为str,而且变为str,具有20位精度。
-
@ivan_pozdeev见上面的update3
-
@AntonyHatchkins round(np.float(a), 8)给出了相同的结果,这就是为什么我说的可能是这个问题或者错误float64
-
可能很有趣:np.float64.__round__ == np.float32.__round__和np.float.__round__ == float.__round__但np.float64.__round__ != float.__round__!因此,round(a)和round(np.float64(a))正在执行不同的代码。
-
@VikasDamodar见上面的Update4
-
@Amadan这是预期的,因为它们返回不同类型的变量。您既不希望本机python round返回np.float64,也不希望np.round返回本机float
-
是。只是说,你可能想要确切地看到numpy的圆形(即np.generic.__round__)以及它与Python的原生float.__round__的不同之处。没有看源,似乎numpy的round没有正确检测到边框。
-
有趣的观察是字符串格式化正常。 '%.8f' % np.float64(5.92270987499999979065)返回'5.92270987'。这可能是numpy的round()功能中的一个错误。
-
@AntonyHatchkins你只是从你的第一个答案中创建一个20精度的字符串,你已经知道两个返回不同所以当你试图使str返回值时它是如何相同的,它不是一个浮点值它是str
-
@VikasDamodar见上面的Update5。您可以将您的想法作为单独的答案发布,因为问题变得混乱。
-
@AntonyHatchkins他们不一样,请点击这里:repl.it/repls/SubstantialHummingFrenchbulldog
-
查看更新4,看起来numpy的round()将最近的二进制值返回到所需的舍入值,即使最小值在十进制输出时可能显示不同。
-
这不是解决方案或解释,但是如何用np.around()替换round()?这样至少你所有的假设都是numpy假设。
-
@casevh你们都忘记了底层数据是用二进制编码的,至少是float64。这样,在数值库中的舍入之上,在打印带小数的数字时进行另一次舍入。 user2357112的答案反映了这一点。显示的差异最有可能大于比较二进制表示时的差异。
-
@jp_data_analysis不确定你建议更换什么,你的意思是什么假设。您可以将此建议作为答案发布吗?
-
@AntonyHatchkins。我的意思是,琐事,np.around(a, 8) == np.around(np.float64(a), 8)。
-
@jp_data_analysis这是非常正确的,但我不确定它可能证明。 当你调用round(np.float64(a))时,它会调用float64.__round__,这与np.around内部执行的操作非常相似。
float.__round__需要特别注意使用正确舍入的双字符串算法生成正确的舍入结果。
NumPy没有。 NumPy文档提到了这一点
Results may also be surprising due to the inexact representation of decimal fractions in the IEEE floating point standard [R9] and errors introduced when scaling by powers of ten.
这样更快,但会产生更多的舍入误差。它会导致您所观察到的错误,以及错误,其中数字甚至更明确地低于截止值仍会被四舍五入:
1 2 3 4 5 6 7 8 9 10 11 12 13
| >>> 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舍入一系列浮点数:
1 2 3 4 5 6 7 8 9 10 11
| 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,而且它是圆形的,甚至是圆形 - 一切都是均匀的。这里没有关系。
-
这听起来令人信服,但在这个特殊的例子中,numpy的结果对我来说更正确。您是否有链接可以阅读有关双字符串算法的更多信息?
-
请参阅问题中的update6。
-
@AntonyHatchkins:查看更新的答案。
-
double和string之间的转换在Python/dtoa.c中实现,基于David Gay的dtoa.c。
-
是的,这是有道理的。但在我的特殊情况下,"正确"的结果是5.92270988,因为最初我通过将11.84541975除以2得到了这个数字。你认为numpy错误得到了这个正确的结果吗?
-
@AntonyHatchkins:你通过将11.84541975除以2来得到你的号码并不能使5.92270988正确。我不明白为什么你会这么想。事实上NumPy的舍入错误恰好符合您的不寻常期望,这是一个侥幸。
-
计算机是为了解决现实问题。计算机语言也是如此。如果我拿33美分的一半,我想得到16美分,而不是round(0.33/2,2)==0.17。是的,十进制类型更适合解决这个问题。但它比numpy慢 - 我承受不起失去那些毫秒。我需要在numpy中获得相同的结果。看起来最佳解决方案在下面的答案中将是round(0.33*100)/100或其1e8等价物。如果有效地解决了舍入算法的这种差异并允许回归测试。
-
@AntonyHatchkins:你正在为这样的代码设置问题;你看到了一个与你的直觉相匹配的四舍五入错误,你指望它继续将你的直觉与进一步的输入相匹配。它不会。
-
如果您碰巧阅读numpy sources multiarray / calculation.c,您会发现它不是侥幸,而是正是我在下面的答案中所描述的以及我在特定情况下需要的内容。当应用于真实案例时,它是比"正确舍入的双字符串"更好的解决方案。
-
谢谢你的回答,这对我很有用,但明天我会重新接受我自己的答案作为一个更正确的答案(所以今天不允许这样做)。
-
我会争辩说"这更快,但更不正确。"评论。这实际上取决于哪种方法"更正确"的问题。
-
@AGNGazer:您认为哪一轮(0.055,2)的结果更正确?我很难想到NumPy的0.059999999999999998比Python的0.06更正确的任何问题。
-
我们是在讨论浮动数字的不精确表示还是关于舍入程序? numpy的结果是否由于0.6的不精确表示? Python能完全代表0.6吗?
-
顺便问一下,你试过float(np.round(0.055, 2))吗?
-
不要浪费你的时间思考一些不存在的事情。这两个是相同的数字>>> f'{round(0.055,2):。20f}''0.05999999999999999778'>>> f'{np.round(0.055,2):。20f}''0.05999999999999999778'这是只是numpy的repr()比浮动更诚实。
-
@AGNGazer:嗯,这就是我试图从内存中重现这个例子的结果 - 0.055是错误的输入
-
看一下非记忆中的例子,我想到的例子是针对不同的操作顺序,所以我没有任何可能改变主意的事情。
-
user2357112,@ AGNGazer plz检查我的另一个答案 - 第一个得到了太多的投票。
是的,另一种处理这类事情的方法是使用在python3中不那么慢的Decimal:
1 2 3 4
| %%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) |
tldr
Builtin round和numpy.round使用不同的舍入算法。对于大多数数字,它们的结果是相同的,但对于某些角落情况则大不相同
两者都适用于某些用途。
对于标量,round更快,对于数组,np.round更快。
说明
&NBSP; ?&NBSP; Builtin round使用直接检查所请求的十进制数字的方法。无论发生什么事情(即使它是...... 499999),它向下四舍五入,并且将5舍入到最近的偶数,除非之后有一个1(例如...... 500001),在这种情况下它会向上舍入。
&NBSP; ?&NBSP; np.round将数字乘以所请求的10的幂,通过普通规则舍入到最接近的int,然后再除以10的相同幂。
它为0.33 / 2等案例提供了更可预测的结果:
1 2 3 4 5 6
| >>> 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 2 3 4 5 6
| >>> 1.09/2
0.545
>>> round(1.09/2, 2)
0.55
>>> np.round(1.09/2, 2)
0.55 |
我能想到的唯一解决方法是
1 2
| >>> round(round(1.09*100)/2)/100
0.54 |
哪个有效,但远非普遍。
-
"对于处理金钱的应用程序" - 哦,你是用这个赚钱吗?二进制浮点绝对不是合法的工具。
-
np.round不会试图猜测用户想要什么。 事实上,它使用的算法倾向于比Python的正确舍入算法更经常地产生看起来像正确结果的东西。 但并非总是如此:例如,np.round(0.545, 2)将提供0.55,而不是0.54,而np.round(0.575, 2)将提供0.57而不是0.58。
-
@ user2357112我在下面的另一个答案中解决了这个问题。 是的,在处理金钱时,十进制类型更适合。 但它比numpy慢。 我正在处理数组。 此外,pandas不适用于Decimal。
-
@MarkDickinson是的,这是一个问题。 我能想到的最好的事情是round((round(1.09*100)/2))/100 == 0.54。
-
从答案中删除了可疑部分。
-
@ user2357112 for cryptomoney,所以它并不坏;)
在我的特殊情况下,一种非常直接的方法来解决这两个函数之间的差异以获得一致的结果是通过乘法和除法。
对于我的应用程序,它似乎比native round更好地工作,给出与np.round相同的结果:
1 2 3 4
| '%.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次)(给出与np.round相同的结果):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| %%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) |
UPDATE3
Python的内置round使用了一种直接的方法,它只对完全可表示的二元有理数进行"四舍五入"规则,如0.375(这是一个整数除以2的精确幂),从而有效地将此规则替换为所有其他带有'围绕领带号码的数字,恰好有49999表示向下并且恰好以50001向上结束'。我不确定这个算法是好还是坏,但绝对不太适合手动检查。
-
对于float.__round__,Round-to-even不仅会为2的负幂提供支持;它可以用于完全可表示的二元有理数,例如0.375。
-
@ user2357112谢谢,纠正