如何保持浮点/双精度算术确定性?

Posted

技术标签:

【中文标题】如何保持浮点/双精度算术确定性?【英文标题】:How to keep float/double arithmetic deterministic? 【发布时间】:2017-10-17 17:44:57 【问题描述】:

如果我们使用双精度和浮点运算的算法,我们如何保证在 Python 和 C、x86 和 x64 Linux 和 Windows 计算机以及 ARM 微控制器中运行它的结果是相同的?

我们正在使用一种算法:

双+双 双精度+浮点数 双倍exp(双倍) 浮动 * 浮动

在同一台计算机上,为 x86 和 x64 MinGW 编译它会产生不同的结果。该算法进行了大量的数学运算,因此任何小错误最终都会产生影响。

现在 ARM mcu 实现与 x86 的结果相同,但看到这个后我不确定是否正确。

编辑

在这种情况下,精度损失不是问题,只要在所有实现中都相同

编辑 2

我发现这些链接非常有用,一些提示已经在 cmets 中:

Is floating point math deterministic? Floating point determinism

【问题讨论】:

我认为你只能依赖一个非常具体的配置,因为 C 不要求浮点的具体实现(不知道关于 python) 通常 x86 上的问题是 x87 寄存器是 80 位的,因此,只要编译器不溢出它们,您将获得比 64 位“正确”更高的精度双精度算术。尝试使用-ffloat-store 看看x86 和x86_64 是否给出相同的结果。 如果您非常需要一致性,您将不得不放弃原生 FP 实现并使用一些自制/第三方可移植库。我会推荐定点... 使所有 FP 变量 volatile 并且从不使用复合方程:sum = a+b*c --> product = b*c; y = a + product; 确保舍入模式匹配并禁用 FP 优化。对于数学库 - 自己动手。这将显着减少变化并降低性能。 这可能是由于舍入默认值或编译器“优化”的差异吗?测试算法是否不稳定的一个好方法是使用不同的舍入设置重新运行它。在 C99 中,您有 fenv 来控制它,对于 Python,请尝试我的 pyfenv 模块。 【参考方案1】:

如果我们使用双精度和浮点运算的算法,我们如何保证在 Python 和 C、x86 和 x64 Linux 和 Windows 计算机以及 ARM 微控制器中运行它的结果是相同的?

一般来说,除非您仔细实施自己的 FP 操作,否则您无法做到这一点。如果您使用各种语言的标准运算符和库以及底层浮点硬件,则无法确保结果在不同实现之间的精确再现性。

首先,浮点数的内部表示存在问题。 C 没有指定要使用的表示形式,即使其他一切都相同,这意味着您不能依赖在不同实现(例如 x86_64 和 ARM)上运行的同一个 C 程序来计算相同的结果。

实际上,现在大多数人都使用 IEEE 754 浮点格式,而 CPython 使用底层 C 实现的 double 类型来支持它的浮点数。然而,即便如此,IEEE 也允许在一致的实现之间存在一定的少量变化。即使是要求严格遵守 IEEE 规范的指令和编译选项也无法完全解决这个问题。

此外,您指定要在 C 和 Python 中同时处理 doublefloat,但 Python 没有 float 的本机模拟。它的本机浮点格式(可能)对应于 C double。对不同浮点数据类型执行的操作必然会产生不同的结果,即使操作数在数值上是等效的,并且差异会在类型转换中持续存在,例如将double 结果转换为float

在(机器)代码生成级别还需要考虑其他细节,例如是否或何时将中间结果从 FPU 寄存器复制到主存储器(可能涉及舍入)以及操作的顺序执行。

我们正在使用一种算法:

double + double
double + float
double exp(double)
float * float

如果您想最小化计算值的差异,那么首先选择一种浮点数据类型,并在任何地方使用它。为了 Python 和 C 实现之间的一致性,应该是double

您还应该考虑禁用所有可能会更改 FP 操作评估顺序的优化。这可能是所有优化。如果您的 C 编译器中有选项可以强制执行严格的 IEEE 一致性,请打开这些选项。

您还应该在所有相关平台上测试exp() 函数的等效性。您可能需要提供自己的实现。


无论您做什么,您都应该认识到,如果您的各种实现产生不同的结果,尽管在某种算法意义上都是正确的,那么这本身就是一个结果。它告诉你一些关于计算的真实精度的信息,如实现的那样。

您绝不能忘记,大多数计算机 FP 操作都会产生近似结果,因此即使您确实设法让所有实现产生相同的结果,但这并不意味着这些结果在绝对意义上一定比其他结果更正确附近的 FP 值。如果要求数值一致性,那么您应该根据结果的特定精度对其进行量化,以能够提供该精度的方式实施您的算法,并忽略高于所选精度的差异。

【讨论】:

添加“实际上,现在大多数人都使用 IEEE 754 浮点格式”,除了规范中难以实现或错误实现的各个角落。许多人努力坚持但没有做到。示例:我对 fma() 的弱/损坏实现感到失望【参考方案2】:

这很难。双精度和浮点数在 C 或 C++ 标准中没有形式化,其准确性取决于编译器/cpu 实现。例如 float 和 double 都允许相同。

来自 C++17 草案(其他论文中类似) basic.fundamental

共有三种浮点类型:float、double 和 long double。 double 类型提供的精度至少与 float 一样,long double 类型提供的精度至少与 double 一样。 float 类型的值集是 double 类型的值集的子集; double 类型的值集是 long double 类型的值集的子集。浮点类型的值表示是实现定义的。 [ 注:本文档对浮点运算的准确性没有要求;另见 [support.limits]。 —— 尾注]

我认为 C 或 C++ 标准中没有提到 IEEE 754。

Python 派生出这一点,其中浮动类型在 C 实现中被引用,其中形式化为 also up to implementaion

共有三种不同的数字类型:整数、浮点数和复数。此外,布尔值是整数的子类型。整数具有无限的精度。浮点数通常在 C 中使用 double 实现;

【讨论】:

这是非常有用和有趣的,但它并没有真正回答问题。

以上是关于如何保持浮点/双精度算术确定性?的主要内容,如果未能解决你的问题,请参考以下文章

如何舍入双精度值但保持尾随零

浮点型数据的精度是啥意思

Scilab中浮点计算的精度是多少?

单精度、双精度各有几位小数?

如何将模数用于浮点/双精度?

浮点算术运算的精度是多少?