【问题标题】:Why is there different asinh output from numpy depending on OS?为什么根据操作系统的不同,numpy 的 asinh 输出会有所不同?
【发布时间】:2019-04-04 21:49:45
【问题描述】:

我试图在操作系统中保持一致的浮点计算结果,但在新系统上进行测试时,我遇到了关于 numpy 和 arcsinh 的奇怪回归。这是一个最小的工作示例,它在不同系统中的行为有所不同。

#!/usr/bin/env python
import struct

from numpy import (array, arcsinh, float32)


def float_to_hex(f):
    return hex(struct.unpack('<I', struct.pack('<f', f))[0])


numpy_result = arcsinh(array([3.0], dtype=float32))[0]
print("asinh(3.0):", numpy_result, float_to_hex(numpy_result))

在 Centos 7 和 Ubuntu 16.04 上,我得到以下结果: asinh(3.0): 1.8184464 0x3fe8c2da

在 Ubuntu 18.04(和一位同事说的 Windows)上,我得到以下结果: asinh(3.0): 1.8184465 0x3fe8c2db

很高兴了解为什么会发生这种情况以及如何在系统之间获得一致的结果。理想情况下坚持使用 32 位浮点解决方案。是否有一些我忽略了跨操作系统更改的 numpy 选项?

值得注意的是,我无法用 C 程序重现这一点。使用 GLIBC 的 asinh(32 位浮点 3.0)我 总是 得到 1.8184465 的新结果,无论我使用什么系统,它都是 0x3fe8c2db 十六进制表示。这似乎是特定于 numpy 的。

我的工作 C 示例:

#include <stdio.h>
#include <math.h>

int main() {
    float value = asinhf(3.0f);
    unsigned int hexValue = *(unsigned int *)&value;
    printf("Plain value: %.7f\n", value);
    printf("Hex value: 0x%8x\n", hexValue);
    return 0;
}

我还可以验证跨系统正在使用完全相同的 numpy 版本。在这种情况下,它是 1.15.3。 numpy 包是从各处的轮子安装的,因此安装了相同的共享对象库。为了我的理智,我通过在所有系统上对所有库运行 file 操作来仔细检查这些库。

我相信根据 IEEE 754,5 的最后一个有效数字(对于 3.0 的反正弦)是正确的,因为它应该从零舍入。但是,结果一致的解决方案对我来说更重要。

感谢您的宝贵时间。

【问题讨论】:

  • 您是否也看到了 asinh(3) 的 64 位浮点值的区别?
  • 你能展示你用于测试的C代码吗?请注意,如果您直接执行 asinh(3.0) 之类的操作,则 gcc 将使用 MPFR 进行完美正确的 compile-time 评估,因此您不会触及 libm 实现asinh 在这种情况下。
  • 但总的来说,希望在libm 实现中获得可重复的结果是乐观的,即使假设采用 IEEE 754 格式。 NumPy 在此处受操作系统的libm 的摆布。
  • 我认为来自 Eric Postpischil 的this answer 在这里是相关的:“如果使用数学库例程(例如 cos 和 log),这是另一个问题,因为它们很难很好地计算,而且不同实现提供了不同的近似值。”
  • 使用round-to-nearest-ties-to-even,对于asinh(3),正确舍入的32位float是由0x3fe8c2db(1.81844651699066162109375)表示的,但这不是因为IEEE- 754 说你应该从零四舍五入。舍入规则是用户可选择的,默认值通常是舍入到最接近的可表示值,并与偶数低位相关。

标签: python numpy floating-point operating-system


【解决方案1】:

所以我发现了为什么不同系统的答案是不同的,以及我以前没有看到它的原因。但是,我仍然不确定如何获得一致的结果。

正如 Mark Dickinson 在 cmets 中指出的那样,我忽略了 gcc 正在使用 MPFR(具有正确舍入的多精度浮点)执行编译时优化。在生成的二进制文件上运行 ldd 表明 libm 根本没有被动态加载。我用 clang 重新编译了我的 C 示例,并在 libm 中动态链接,你瞧,我得到了与跨系统通过 python/numpy 所做的完全相同的结果。结果是asinhf(3.0f); 在旧系统上四舍五入,在新系统上四舍五入。

所以这在某些时候看起来像是 libm 库更新。

具体来说,至少在 GLIBC 的 2.23 和 2.27 之间发生了变化。

如果有人对跨系统进行一致的舍入有任何建议,我将不胜感激。我怀疑无论旧系统的精度如何,都可能存在不正确的舍入。

感谢您的宝贵时间。

【讨论】:

  • 请注意,虽然您已经在这种情况下发现了问题,但数学库(提供 asinh 等的例程)通常不符合 IEEE-754。为这些例程实现正确的舍入是很困难的,没有人可以证明对普通数学库中的所有数学库例程(主要操作系统中默认使用的任何那些,甚至在次要操作系统中使用的)都做过,也没有人证明做过在有限的运行时间内。 (可能有一些特殊的版本会坐下来搅动,直到它们得到一个经过验证的正确舍入结果,运行时间没有已知的限制。)
  • 由于上述原因,数学库是使用各种近似值实现的,这些近似值部分是工程,部分是艺术。不同团队实现的不同库会得到不同的结果。因此,只要 numpy 和 Python 依赖于底层数学库,结果可能会因系统而异。
猜你喜欢
  • 2012-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多