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

时间:2016-01-28 08:37:12

标签: python numpy floating-point floating-accuracy

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

问题出现了,因为我试图重新缩放某些数据,以便min和max匹配另一个数据集。但是,我重新调整后的数据略有偏差(大约小数点后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拥有相同的指数和/或有效数字数字将变得相同。

5 个答案:

答案 0 :(得分:7)

这取决于你所说的“尾数。”

在内部,浮点数在基数2中使用科学记数法存储。因此,如果你的意思是 base 2 尾数,它实际上很容易:只乘以或除以2的幂(不是10的幂) ),并且尾数将保持不变(假设指数不超出范围;如果确实如此,您将被钳制到无穷大或零,或者根据建筑细节可能进入denormal numbers。重要的是要了解当您重新调整2的幂时,小数扩展将不匹配。这是用这种方法保留的二进制扩展。

但是,如果你的意思是基数为10的尾数,不是,浮点数是不可能的,因为重新调整的值可能无法准确表示。例如,1.1不能在基数2(具有有限数字的位数)中精确表示,其方式与1/3不能在基数10中表示(具有有限的数字位数)。因此,将1/10重新缩小1/10不能完全准确地完成:

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

但是,您可以使用decimal来执行后者。小数在基数10中起作用,并且在基数10重新缩放方面将按预期运行。它们还提供了相当多的专用功能来检测和处理各种精度损失。但是小数don't benefit from NumPy speedups,所以如果您要处理大量数据,它们可能对您的用例来说效率不高。由于NumPy依赖于对浮点的硬件支持,并且大多数(所有?)现代架构都没有为基础10提供硬件支持,因此这不容易解决。

答案 1 :(得分:3)

尝试用

替换第二行
f2 = f2*np.max(f1) + (1.0-f2)*np.min(f1)

说明:有两个地方可能会出现差异:

步骤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。建议的表达式稍微稳定一些。

有关详细说明,请参阅 David Goldberg What Every Computer Scientist Should Know About Floating-Point Arithmetic

答案 2 :(得分:2)

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 significant figures左右),这也是太多有效数字,因此会删除尾随小数。而且,有些"短"正如@ Kevin&{39} answer所述,数字无法准确表示。有关详情,请参阅,例如here。 (搜索"浮点截断舍入错误"更多内容。)

这是一个证明问题的例子:

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的comment,我使用了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的原始范围。)

重新使用舍入来解决您的问题,我推荐这个。四舍五入的问题 - 除了不必要地降低数据准确性这一事实 - 非常接近的数字可以在不同的方向上进行。 E.g。

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]

这与以下事实有关:尽管讨论与这么多有效数字相匹配的数字很常见,但人们实际上并没有在计算机上这样比较它们。您计算差值然后除以正确的数字(对于相对误差)。

重做mantissas和exponents,请参阅math.frexpmath.ldexp,记录here。我不建议你自己设置这些(考虑两个非常接近但具有不同指数的数字,例如 - 你真的想设置尾数)。如果你想确保数字完全相同(并且类似于最小值),那么直接将f2的最大值明确地设置为f1的最大值要好得多。

答案 3 :(得分:0)

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)

answer:

  

150   100

答案 4 :(得分:-2)

这是一个带小数的

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