IEEE 754-2008 是确定性的吗?

Is IEEE 754-2008 deterministic?

如果我从相同的值开始,并对双精度 64 位 IEEE 754-2008 值执行相同的原始操作(加法、乘法、比较等),我会得到相同的结果吗?底层机器?

更具体地说:由于 ECMAScript 2015 指定数字值为

primitive value corresponding to a double-precision 64-bit binary format IEEE 754-2008 value

我可以得出结论,相同的操作在这里产生相同的结果,与环境无关吗?

(这里有很多脚注是为了避开人群,但它们不会影响您对 ECMAScript 的问题。)

IEEE 754

If I start with the same values, and perform the same primitive operations (addition, multiplication, comparision etc.) on double-precision 64-bit IEEE 754-2008 values, will I get the same result, independent of the underlying machine?

是。

IEEE 754-2008(和 IEEE 754-2019)标准精确定义了所有浮点值的加、减、乘、除和平方根运算,不同 NaN 值之间的区别除外。1 标准2 的实施同意所有输入。 这同样适用于三向比较(<、= 或 >,在数字上定义,包括无穷大;在 NaN 上引发异常)或四向比较(<、=、> 或无序,在所有浮点值上定义包括 NaN)。

不仅在所有输入上都精确定义了这五个算术运算,而且对于数字输入,它们也被精确定义为正确舍入:浮点加法运算 ⊕ 被定义为给出 fl( + ),即实数 sum + 根据当前舍入模式舍入的结果,3 默认为 return 最接近的浮点数,或者,如果并列,最接近的最低有效数字为偶数。

ECMAScript 2015(和 2021)

More concretely: Since ECMAScript 2015 specifies that a number values is

primitive value corresponding to a double-precision 64-bit binary format IEEE 754-2008 value

我可以得出结论,相同的操作在这里产生相同的结果,与环境无关吗?

是。

ECMAScript 2015 中对数字的操作 +-*/ 均根据 IEEE 754 在所有输入上进行了精确定义。4 比如addition in ECMAScript 2015的定义具体说明:

The result of an addition is determined using the rules of IEEE 754-2008 binary double-precision arithmetic:

addition in ECMAScript 2021 的定义基本保持不变,更新为引用 IEEE 754-2019:

The abstract operation Number::add takes arguments x (a Number) and y (a Number). It performs addition according to the rules of IEEE 754-2019 binary double-precision arithmetic, producing the sum of its arguments.

类似地,equality in ECMAScript 2015 and equality in ECMAScript 2021 is defined in agreement with IEEE 754-2008 and IEEE 754-2019, although without an explicit citation. Relational operators in ECMAScript 2015 and relational operators in ECMAScript 2021 都实现了 IEEE 754 有序比较概念,returning false 当任一输入为 NaN 时,否则为适当的顺序。

Math.sqrt in ECMAScript 2015, and Math.sqrt in ECMAScript 2021,允许 return 平方根的实现定义的近似值(受边角情况的约束),即使 IEEE 754 精确定义了平方根运算并且已经完成所以从 IEEE 754-1985 开始。 但是,实际上,实现无法 return IEEE 754 要求的正确舍入结果的可能性极小。

注:除四五个基本算术运算(+-以外的许多运算 , *, /; Math.sqrt) 允许并且很可能会因实施而异。 例如,一种实现可能对 Math.log1p 使用简单的多项式近似,而另一种实现可能使用 table 驱动的一组近似,对某些输入给出略有不同的结果。 这有时被用作浏览器指纹识别的载体。 但是任何近似 you 仅使用基本算术运算的实现在所有 ECMAScript 实现中都是一致的。

运算符 % in ECMAScript 2015 and % in ECMAScript 2021 为所有输入精确定义,但不符合 IEEE 754 余数运算:ECMAScript % 使用截断除法,而 IEEE 754 余数使用 round-to-nearest/ties-to-even分配。 (ECMAScript % 在 C 中是 fmod,而 IEEE 754 余数在 C 中是 remainder。)

其他语言

以上答案并不总是适用于其他语言。 例如,绝大多数 C 实现为 double 提供 IEEE 754 binary64 算术,为 float 提供 binary32 算术,但 C 标准允许它们在表达式 [=] 中使用不同的算术规则 152=],前提是他们通过 FLT_EVAL_METHOD 宏指定规则是什么:

Except for assignment and cast (which remove all extra range and precision), the values yielded by operators with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type. The use of evaluation formats is characterized by the implementation-defined value of FLT_EVAL_METHOD:

  • -1 indeterminable;
  • 0 evaluate all operations and constants just to the range and precision of the type;
  • 1 evaluate operations and constants of type float and double to the range and precision of the double type, evaluate long double operations and constants to the range and precision of the long double type;
  • 2 evaluate all operations and constants to the range and precision of the long double type.

All other negative values for FLT_EVAL_METHOD characterize implementation-defined behavior.

(C11,§5.2.4.2.2:浮动类型的特征 <float.h>,¶9,第 30 页)

这意味着当实现将FLT_EVAL_METHOD定义为2时,像

这样的函数
double
naive_fma(double x, double y, double z)
{
    return x*y + z;
}

将实施就好像它已经被写成:

double
naive_fma(double x, double y, double z)
{
    return (long double)x*z + z;
}

C 在 Intel IA-32 架构(“i386”)上的实现通常以这种方式工作:它们使用 Intel x87 浮点单元计算 80 位二进制浮点运算中的表达式64 位精度(“双扩展精度”),然后舍入到 IEEE 754 binary64,只要结果存储在 double 变量中,作为 double 参数传递,或显式转换为 double.5

但是,ECMAScript 不允许这种计算表达式的方法,因此您不必担心。 通过编译为 ECMAScript 的 C 实现以显而易见的方式简单地将 FLT_EVAL_METHOD 定义为 0.


1 NaN 有效载荷的内容可能因实现而异。 但是,结果是否为 NaN,以及 NaN 结果是发信号还是静默,由标准定义。

2 某些硬件还提供非标准操作模式,如清零,这会导致操作 return 零,而在 IEEE 754 语义下它们会 return 次正规数;在那种情况下,硬件不是标准的实现。 如果您启用这些模式,那么您可能会得到不同的答案,但通常不会启用它们,并且它们违反了 Sterbenz lemma 等数值算法经常假设的定理,因此它们仅用于专门的应用程序。 ECMAScript 不支持刷新为零或其他非标准操作模式,我所知道的任何实现也不支持:您可以依赖 IEEE 754 中定义的逐渐下溢到次正规。

3 IEEE 754 允许实现保持动态舍入模式,定义了四个舍入方向:to-nearest/ties-to-even、向上(向正无穷大)、向下(向负无穷大)和向零。 在某些环境中,程序可以查询和更改当前舍入模式,例如在 C 中使用 fegetroundfesetround,尽管对此的工具链支持通常是有限的,它主要用于将小扰动注入数值算法检查输出中是否存在指示算法问题的剧烈变化。 ECMAScript 不支持更改舍入模式,据我所知,任何实现也不支持:您只需处理默认值 round-to-nearest/ties-to-even。

4 ECMAScript 的语义仅区分单个 NaN 值;在 ECMAScript 中没有 NaN 有效负载或信号与安静 NaN 的概念。 在底层,两个 NaN 可能以不同的位模式存储,但 ECMAScript 不在语义上区分它们,并且不提供区分它们或在底层检查位模式的方法。

5 以更高的精度计算表达式有时会导致双舍入错误——例如,将 0x1p+53 和 0x1.7ffp+1 相加,第一次舍入为 64 位精度将得到 0x1.000000000000018p+53,因此第二次舍入为 53 - 位精度给出 0x1.00000000000002p+53,而具有 53 位精度的正确舍入和为 0x1.00000000000001p+53。 那么为什么要这样做呢? 在实践中,通过使用更高的中间精度,它几乎总能提高数值算法的精度:您可以承受损失 64 位精度的数千个 ulp,但仍能得到在 53 位精度的几个 ulp 以内的答案。