Jas*_*son 8 python floating-point numpy floating-accuracy
这是一个有趣的问题,我试图在前几天工作.是否有可能强制一个的有效数或指数与Python中的float另一个相同float?
问题出现了,因为我试图重新缩放某些数据,以便min和max匹配另一个数据集.但是,我重新调整后的数据略有偏差(大约小数点后6位),这足以引起问题.
提出一个想法,我有f1和f2(type(f1) == type(f2) == numpy.ndarray).我想要np.max(f1) == np.max(f2) and np.min(f1) == np.min(f2).为此,我做到了:
import numpy as np
f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
Run Code Online (Sandbox Code Playgroud)
结果(仅作为示例)将是:
np.max(f1) # 5.0230593
np.max(f2) # 5.0230602 but I need 5.0230593
Run Code Online (Sandbox Code Playgroud)
我最初的想法是,强制指数float将是正确的解决方案.我找不到多少,所以我为我的需要做了一个解决方法:
exp = 0
mm = np.max(f1)
# find where the decimal is
while int(10**exp*mm) == 0
exp += 1
# add 4 digits of precision
exp += 4
scale = 10**exp
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
Run Code Online (Sandbox Code Playgroud)
现在 np.max(f2) == np.max(f1)
但是,还有更好的方法吗?我做错什么了吗?是否有可能重塑一个float类似于另一个float(指数或其他方式)?
编辑:按照建议,我现在使用:
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
Run Code Online (Sandbox Code Playgroud)
虽然我的上述解决方案将起作用(对于我的应用程序),但我很想知道是否有一种解决方案可以某种方式强制float使用相同的指数和/或有效数字,以使数字变得相同.
这取决于你所说的"尾数".
在内部,浮点数使用科学记数法存储在基数2中.因此,如果你的意思是基数2尾数,它实际上非常容易:只乘以或除以2的幂(不是10的幂),并且尾数将保持不变(如果指数没有超出范围;如果确实如此,你将被钳制到无穷大或零,或者可能根据建筑细节进入非正规数字).重要的是要了解当您重新调整2的幂时,小数扩展将不匹配.这是使用此方法保留的二进制扩展.
但是,如果你的意思是基数为10的尾数,不是,浮点数是不可能的,因为重新调整的值可能无法准确表示.例如,1.1不能在基数2(具有有限数字的位数)中精确表示,其方式与1/3不能在基数10中表示(具有有限的数字位数).因此,将1/10重新缩小1/10不能完全准确地完成:
>>> print("%1.29f" % (11 * 0.1))
1.10000000000000008881784197001
Run Code Online (Sandbox Code Playgroud)
但是,您可以使用decimals来执行后者.小数在基数10中起作用,并且在基数10重新缩放方面将按预期运行.它们还提供了相当多的专用功能来检测和处理各种精度损失.但小数不会从NumPy加速中受益,所以如果你有大量的数据可供使用,它们可能对你的用例来说效率不高.由于NumPy依赖于对浮点的硬件支持,并且大多数(全部?)现代架构不为基础10提供硬件支持,因此这不容易解决.
长话短说
使用
f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1)
Run Code Online (Sandbox Code Playgroud)
并确保使用双精度,通过查看绝对或相对差异来比较浮点数,避免舍入以调整(或比较)浮点数,并且不要手动设置浮点数的基础组件。
细节
正如您所发现的,这不是一个很容易重现的错误。但是,使用浮点数可能会出错。例如,相加1 000 000 000 + 0 . 000 000 000 1得到,但即使对于双精度(支持大约15 个有效数字1 000 000 000 . 000 000 000 1),这也太多了有效数字,因此尾部小数被删除。此外,正如 @Kevin 的回答中所述,一些“短”数字无法准确表示。例如,请参阅此处了解更多信息。(搜索诸如“浮点截断舍入错误”之类的内容以获取更多信息。)
这是一个确实演示了问题的示例:
import numpy as np
numpy.set_printoptions(precision=16)
dtype=np.float32
f1 = np.linspace(-1000, 0.001, 3, dtype=dtype)
f2 = np.linspace(0, 1, 3, dtype=dtype)
f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
print (f1)
print (f2)
Run Code Online (Sandbox Code Playgroud)
输出
[ -1.0000000000000000e+03 -4.9999951171875000e+02 1.0000000474974513e-03]
[ -1.0000000000000000e+03 -4.9999951171875000e+02 9.7656250000000000e-04]
Run Code Online (Sandbox Code Playgroud)
根据@Mark Dickinson 的评论,我使用了 32 位浮点。这和你报的错误是一致的,相对误差在10^-7左右,第7位有效数字左右
In: (5.0230602 - 5.0230593) / 5.0230593
Out: 1.791736760621852e-07
Run Code Online (Sandbox Code Playgroud)
去dtype=np.float64使事情变得更好,但它仍然不完美。上面的程序给出了
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
[ -1.0000000000000000e+03 -4.9999950000000001e+02 9.9999999997635314e-04]
Run Code Online (Sandbox Code Playgroud)
这并不完美,但通常已经足够接近了。在比较浮点数时,您几乎不想使用严格相等,因为如上所述,可能会出现小错误。相反,用一个数字减去另一个数字并检查绝对差是否小于某个容差,和/或查看相对误差。参见,例如numpy.isclose。
回到你的问题,似乎应该可以做得更好。毕竟 的f2范围是 0 到 1,所以您应该能够复制 中的最大值f1。问题出在线上
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
Run Code Online (Sandbox Code Playgroud)
因为当 的元素f2为 1 时,您对其执行的操作不仅仅是将 1 乘以 的最大值f1,因此可能会出现浮点算术错误。请注意,您可以将括号相乘f2*(np.max(f1)-np.min(f1)),f2*np.max(f1) - f2*np.min(f1)然后将结果分解- f2*np.min(f1) + np.min(f1)为np.min(f1)*(f2-1)
f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1)
Run Code Online (Sandbox Code Playgroud)
因此,当 的一个元素为f21 时,我们有1*np.max(f1) - np.min(f1)*0。相反,当 的元素为f20 时,我们有0*np.max(f1) - np.min(f1)*1。数字 1 和 0可以准确表示,因此不应有错误。
修改后的程序输出
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
Run Code Online (Sandbox Code Playgroud)
即根据需要。
尽管如此,我仍然强烈建议仅使用不精确的浮点比较(如果需要,可以使用严格的界限),除非您有充分的理由不这样做。浮点运算中可能会出现各种细微的错误,避免这些错误的最简单方法就是永远不要使用精确比较。
上面给出的另一种方法(可能更可取)是将两个数组重新调整到 0 到 1 之间。这可能是程序中最适合使用的形式。(两个数组都可以乘以一个缩放因子,例如原始范围f1,例如 的原始范围。)
重新使用舍入来解决您的问题,我不推荐这样做。舍入的问题(除了它不必要地降低数据的准确性这一事实之外)是非常接近的数字可能会朝不同的方向舍入。例如
f1 = np.array([1.000049])
f2 = np.array([1.000051])
print (f1)
print (f2)
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
print (f1)
print (f2)
Run Code Online (Sandbox Code Playgroud)
输出
[ 1.000049]
[ 1.000051]
[ 1.]
[ 1.0001]
Run Code Online (Sandbox Code Playgroud)
这与以下事实有关:尽管讨论与如此多有效数字匹配的数字很常见,但人们实际上并没有在计算机中以这种方式比较它们。您计算差异,然后除以正确的数字(相对误差)。
关于尾数和指数,请参阅math.frexp和math.ldexp,记录在此处。然而,我不建议您自己设置这些(例如,考虑两个非常接近但具有不同指数的数字——您真的想设置尾数)。如果您想确保数字完全相同(最小值也类似),最好直接将 的最大值显式设置f2为 的最大值。f1
| 归档时间: |
|
| 查看次数: |
646 次 |
| 最近记录: |