【问题标题】:Is it possible to force exponent or significand of a float to match another float (Python)?是否可以强制浮点数的指数或有效数字匹配另一个浮点数(Python)?
【发布时间】:2016-01-28 08:37:12
【问题描述】:

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

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

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

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

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 具有相同的指数和/或有效数字,以便数字变得相同。

【问题讨论】:

  • 根据您正在使用的范围,您也许可以使用np.log10(x) 来解决问题。使用一些虚拟值,我得到 `np.log10(1.2312412) = 0.090343139501527295` 和 np.log10(12.312412) = 1.0903431395015273,但 np.log10(.12312412) = -0.90965686049847261
  • 或者,只要您仅按 10 的幂进行缩放 10**(np.log10(1.2312412)) = 1.231241199999999910**(np.log10(1.2312412)+2) = 123.1241200000000510**(np.log10(1.2312412)-2) = 0.012312411999999998,这意味着尾数中的错误被推到最后一位。跨度>
  • 啊,好吧。 FWIW,“尾数”的浮点使用往往不受欢迎,因为它与尾数作为(以 10 为底)对数的小数部分的旧数学定义相冲突。请改用“有效数字”。
  • 回复f2 *= (np.max(f1)-np.min(f1)) + np.min(f1)。这不应该是f2 = f2 * (np.max(f1)-np.min(f1)) + np.min(f1)。 IE。您想按f1 的范围重新缩放,然后添加f1 的最小值。在我看来,就目前而言,您将 0 到 1 范围乘以 *= 右侧的整个术语。
  • @TooTone:很可能是单精度算术(即 dtype float32 的 NumPy 数组)。

标签: python numpy floating-point floating-accuracy


【解决方案1】:

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

在内部,浮点数使用以 2 为底的科学记数法存储。因此,如果您指的是 以 2 为底的尾数,实际上非常简单:只需乘以或除以 2 的幂(不是 10 的幂) ),并且尾数将保持不变(前提是指数没有超出范围;如果超出范围,您将被限制为无穷大或零,或者可能进入denormal numbers,具体取决于架构细节)。重要的是要了解,当您重新调整 2 的幂时,小数扩展将不匹配。使用此方法保留的是二进制扩展。

但是,如果您指的是以 10 为底的尾数,不,浮点数不可能,因为重新调整后的值可能无法准确表示。例如,1.1 不能以 2 为底(数字有限)精确表示,就像 1/3 不能以 10 为底(数字有限)表示一​​样。因此,将 11 缩小 1/10 无法完美准确地完成:

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

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

【讨论】:

  • IBM System z 机器(从 z10 开始)具有对十进制算术的硬件支持。但据我所知,NumPy 或 Python 都没有使用硬件支持的工具。
【解决方案2】:

尝试将第二行替换为

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

非常详细的解释,请看 What Every Computer Scientist Should Know About Floating-Point Arithmetic David Goldberg。

【讨论】:

  • 请注意,+ (1.0-f2)*np.min(f1) 在我的回答中等同于 -np.min(f1)*(f2-1)
【解决方案3】:

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 的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]

即根据需要。

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

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

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

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

【讨论】:

  • 为了添加上下文,这个数据用于插值(通过时间的东西)。我不一定使用相等操作,但是当我尝试将 f1 时间位置映射到 f2 时间位置时,开始发生奇怪的事情。然后像 np.unique 这样的东西在时间无限时返回超过 2 个值。那有意义吗?这是一个很好的讨论。
  • @Jason。这种情况会有所帮助。你的意思是f1[i]类似于粒子1到达位置i的时间,还是f1[t]是特定1在时间t到达的距离? (对于粒子 2 也是如此。)
  • 这两个数据片段可以被认为是一条加速或减速曲线。但是,我会在不同时间点的曲线之间跳转。随着时间接近无穷大,极值应始终是两个值之一。在我最初的讨论中,这不会发生。跳跃曲线很糟糕,因为一个极值与另一个不匹配。
【解决方案4】:
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

【讨论】:

    【解决方案5】:

    这是一个带小数的

    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
    

    【讨论】:

    • 我认为您收到了负面反馈,因为这是一种解决方法且不准确。就我而言,这会导致数据不连续。如果a[1] - b[1] &lt; 1e-6a[2] - b[2] &gt; 1e-6 可能存在从b[1]b[2] 最多1e-6 的不连续性。在我的情况下,Decimal 也会非常慢。
    • 好吧,也许你还没有理解这个问题,但我们必须做出不连续性,因为我们希望最终的浮动是相同的。该问题尚未定义如何处理四舍五入,只是将四舍五入到小数点后 5 位或 6 位。您当然可以使用相同的逻辑创建一个链式 if 语句,其中 5 个小数表示某些差异,6 个小数表示其他一些差异。十进制是访问尾数位并对其进行操作的最快方法,而无需编写机器代码。
    • 其他解决方案不会产生不连续性。如果您正在使用 Decimal 执行许多操作,则速度非常慢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-13
    • 1970-01-01
    相关资源
    最近更新 更多