是否可以强制浮点数的指数或有效数匹配另一个浮点数(Python)



这是我前几天试图解决的一个有趣的问题。是否可以强制一个float的有效数或指数与 Python 中的另一个float相同?

出现这个问题是因为我试图重新缩放一些数据,以便最小值和最大值与另一个数据集匹配。但是,我重新缩放的数据略有偏差(大约 6 位小数后),这足以导致问题。

为了给出一个想法,我有f1f2type(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)

结果(仅作为示例)将是:

np.max(f1) # 5.0230593
np.max(f2) # 5.0230602 but I need 5.0230593 

我最初的想法是,强制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

现在np.max(f2) == np.max(f1)

但是,有没有更好的方法?我做错了什么吗?是否可以重塑float使其类似于另一种float(指数或其他方式)?

编辑:正如建议的那样,我现在正在使用:

scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)

虽然我上面的解决方案将起作用(对于我的应用程序),但我有兴趣知道是否有一种解决方案可以以某种方式强制float具有相同的指数和/或有效数,以便数字变得相同。

这取决于你所说的"尾数"是什么意思。

在内部,浮点数以 2 为基数使用科学记数法存储。 因此,如果你指的是底数 2 尾数,它实际上非常简单:只需乘以或除以 2 的幂(不是 10 的幂),尾数将保持不变(前提是指数没有超出范围;如果是这样,你会被钳制到无穷大或零,或者根据架构细节进入非正常数字)。 请务必了解,当您以 2 的幂重新缩放时,十进制扩展将不匹配。 这是使用此方法保留的二进制扩展。

但是,如果您指的是底数 10 尾数,不,浮点数是不可能的,因为重新缩放的值可能无法完全表示。 例如,1.1 不能以 2 为底(具有有限位数)精确表示,就像 1/3 不能以 10 为底(具有有限位数)表示的方式大致相同。 因此,将 11 向下缩放 1/10 无法完全准确地完成:

>>> print("%1.29f" % (11 * 0.1))
1.10000000000000008881784197001

但是,您可以使用decimal s执行后者。 小数以 10 为基数工作,并且在以 10 为基数的重新缩放方面将按预期运行。 它们还提供了相当多的专用功能来检测和处理各种精度损失。 但是小数不会从 NumPy 加速中受益,因此如果您有大量数据可供使用,它们对于您的用例来说可能不够有效。 由于 NumPy 依赖于浮点的硬件支持,并且大多数(所有?)现代架构不提供对 base 10 的硬件支持,因此这不容易补救。

尝试将第二行替换为

f2 = f2*np.max(f1) + (1.0-f2)*np.min(f1)

说明:差异可能会在 2 个地方蔓延:

步骤1) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2))

当您检查 np.min(f2)np.max(f2) 时,您得到的正好是 0 和 1 还是类似 1.0000003?

步骤2) f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)

(a-b)+b这样的表达式并不总是产生精确的a,因为舍入误差。建议的表达式稍微稳定一些。

有关非常详细的说明,请参阅每个计算机科学家都应该知道的关于浮点算术的知识 大卫·戈德堡。

TL;DR

f2 = f2*np.max(f1)-np.min(f1)*(f2-1)  # f2 is now between min(f1) and max(f1)

并确保使用双精度,通过查看绝对或相对差异来比较浮点数,避免四舍五入来调整(或比较)浮点数,并且不要手动设置浮点数的基础组件。

正如您所发现的那样,这不是一个很容易重现的错误。但是,使用浮点数可能会出错。例如,将1 000 000 000 + 0 . 000 000 000 1相加得到1 000 000 000 . 000 000 000 1,但即使对于双精度(支持大约 15 个有效数字),这也是太多的有效数字,因此删除了尾随小数。此外,正如@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)

输出

[ -1.0000000000000000e+03  -4.9999951171875000e+02   1.0000000474974513e-03]
[ -1.0000000000000000e+03  -4.9999951171875000e+02   9.7656250000000000e-04]

根据@Mark Dickinson 的评论,我使用了 32 位浮点数。这与你报告的误差一致,相对误差约为 10^-7,大约第 7 个有效数字

In: (5.0230602 - 5.0230593) / 5.0230593
Out: 1.791736760621852e-07

dtype=np.float64会让事情变得更好,但它仍然不完美。上面的程序然后给出

[ -1.0000000000000000e+03  -4.9999950000000001e+02   1.0000000000000000e-03]
[ -1.0000000000000000e+03  -4.9999950000000001e+02   9.9999999997635314e-04]

这并不完美,但通常足够接近。在比较浮点数时,您几乎从不想使用严格相等,因为如上所述可能存在小误差。相反,从另一个数字中减去一个数字,并检查绝对差异小于某个容差,和/或查看相对误差。例如,请参阅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)

因为当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)

因此,当f2的元素为 1 时,我们有 1*np.max(f1) - np.min(f1)*0 .相反,当 f2 的元素为 0 时,我们有 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]

即根据需要。

尽管如此,我仍然强烈建议只使用不精确的浮点比较(如果需要,可以使用严格的边界),除非您有充分的理由不这样做。浮点运算中可能发生各种细微的错误,避免它们的最简单方法是永远不要使用精确比较。

上面给出的另一种方法(可能更可取)是将两个数组重新缩放到 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)

输出

[ 1.000049]
[ 1.000051]
[ 1.]
[ 1.0001]

这与这样一个事实有关,即尽管讨论与如此多的有效数字匹配的数字很常见,但人们实际上并没有在计算机中以这种方式比较它们。您计算差值,然后除以正确的数字(相对误差)。

尾数和指数,见math.frexpmath.ldexp,记录在这里。但是,我不建议自己设置这些(例如,考虑两个非常接近但具有不同指数的数字 - 您真的要设置尾数吗)。如果要确保数字完全相同(最小值也是如此),则最好直接将最大值f2显式设置为最大值f1

def rescale(val, in_min, in_max, out_min, out_max):
    return out_min + (val - in_min) * ((out_max - out_min) / (in_max - in_min))
value_to_rescale = 5
current_scale_min = 0
current_scale_max = 10
target_scale_min = 100
target_scale_max = 200
new_value = rescale(value_to_rescale, current_scale_min, current_scale_max, target_scale_min, target_scale_max)
print(new_value)
new_value = rescale(10, 0, 10, 0, 100)
print(new_value)

答:

150 100

这是一个小数

from decimal import Decimal, ROUND_05UP
num1 = Decimal('{:.5f}'.format(5.0230593))  ## Decimal('5.02306')
num2 = Decimal('{}'.format(5.0230602))  ## Decimal('5.0230602')
print num2.quantize(num1, rounding=ROUND_05UP) ## 5.02306

编辑** 我对为什么我得到这么多负面反馈有点困惑,所以这是另一种不使用小数的解决方案:

a = 5.0230593
b = 5.0230602
if abs(a - b) < 1e-6:
    b = a

最新更新