IEEE 754-2008 是确定性的吗?
Posted
技术标签:
【中文标题】IEEE 754-2008 是确定性的吗?【英文标题】:Is IEEE 754-2008 deterministic? 【发布时间】:2017-02-11 22:08:21 【问题描述】:如果我从相同的值开始,并对双精度 64 位 IEEE 754-2008 值执行相同的原始操作(加法、乘法、比较等),我会得到相同的结果,而与底层无关机器?
更具体地说:由于ECMAScript 2015 指定数字值是
对应于双精度 64 位二进制的原始值 格式 IEEE 754-2008 值
我是否可以得出结论,相同的操作在这里产生相同的结果,与环境无关?
【问题讨论】:
请注意,这个问题与代数恒等式无关,可能存在也可能不存在。 本题读者可能感兴趣的一些相关链接:randomascii.wordpress.com/2013/07/16/floating-point-determinism 可能相关问题:***.com/questions/10334782/… Can precision of floating point numbers in javascript be a source of non determinism?的可能重复 我也对这个问题感兴趣,但我想注意,现在的替代方案可能是某些本地语言(即 C 或 Rust)+ WebAssembly 中的定点算术,其 API 暴露于 JavaScript 端.将每个运算符公开为对包含定点表示的TypedArray
s 进行操作的函数。这是在每个可以想象的平台上 100% 保证的确定性。
【参考方案1】:
(这里有很多脚注是为了避开那些实际上很受欢迎的人,但它们不会影响您对 ECMAScript 的问题。)
IEEE 754
如果我从相同的值开始,并对双精度 64 位 IEEE 754-2008 值执行相同的原始操作(加法、乘法、比较等),我会得到相同的结果,而与底层无关机器?
是的。
IEEE 754-2008(和 IEEE 754-2019)标准精确定义了对所有浮点值的加法、减法、乘法、除法和平方根运算,不同 NaN 值之间的区别除外。1 标准2 的实现同意所有输入。 三路比较(,定义在数字上,包括无穷大;引发 NaN 异常)或四路比较( 或无序,定义在所有浮点值上)也是如此包括 NaN)。
这五个算术运算不仅在所有输入上都精确定义,而且对于数字输入,它们也被精确定义为正确舍入:浮点加法运算? ⊕ ? 定义为给出 fl(? + ?),这是根据当前舍入模式对实数和 ? + ? 进行舍入的结果,3 默认返回最接近的浮点数,或者,如果出现平局,则返回最接近的浮点数其最低有效位为偶数。
ECMAScript 2015(和 2021)
更具体地说:由于ECMAScript 2015 指定数字值是
对应于双精度 64 位二进制的原始值 格式 IEEE 754-2008 值
我是否可以得出结论,相同的操作在这里产生相同的结果,与环境无关?
是的。
ECMAScript 2015 中对数字的+
、-
、*
和/
操作都是在所有输入上精确定义的,符合 IEEE 754。4 例如, addition in ECMAScript 2015 的定义明确指出:
使用 IEEE 754-2008 二进制双精度算术规则确定加法的结果:
addition in ECMAScript 2021 的定义基本保持不变,更新为引用 IEEE 754-2019:
抽象操作 Number::add 接受参数 x(一个数字)和 y(一个数字)。它根据 IEEE 754-2019 二进制双精度算术的规则执行加法,产生其参数的总和。
同样,equality in ECMAScript 2015 和 equality in ECMAScript 2021 的定义与 IEEE 754-2008 和 IEEE 754-2019 一致,尽管没有明确引用。 Relational operators in ECMAScript 2015 和 relational operators in ECMAScript 2021 都实现了 IEEE 754 有序比较的概念,当任一输入为 NaN 时返回 false
,否则返回适当的排序。
Math.sqrt
in ECMAScript 2015 和 Math.sqrt
in ECMAScript 2021 被允许返回一个实现定义的近似值(受关于极端情况的约束)到平方根,即使 IEEE 754 精确定义了平方根运算并且自从从 IEEE 754-1985 开始。
但实际上,实现返回 IEEE 754 要求的正确舍入结果的可能性极小。
注意:许多操作除了四个或五个基本算术运算(+
、-
、*
、/
;Math.sqrt
) 被允许并且很可能会因实施而异。
例如,一种实现可能对Math.log1p
使用简单的多项式逼近,而另一种实现可能使用表驱动的一组逼近,对某些输入给出略微不同的结果。
这有时被用作浏览器指纹识别的载体。
但是,您仅使用基本算术运算实现的任何近似值在所有 ECMAScript 实现中都是一致的。
运算符 %
in ECMAScript 2015 和 %
in ECMAScript 2021 是为所有输入精确定义的,但与 IEEE 754 余数运算不一致:ECMAScript %
使用截断除法,而 IEEE 754 余数使用舍入到最近/结- 到偶数除法。
(ECMAScript %
在 C 中是 fmod
,而 IEEE 754 余数在 C 中是 remainder
。)
其他语言
上述答案并不总是适用于其他语言。
例如,绝大多数 C 实现为double
提供 IEEE 754 binary64 算术,为float
提供 binary32 算术,但 C 标准允许它们在表达式中使用不同的算术规则,前提是它们通过FLT_EVAL_METHOD
宏指定规则是什么:
除了赋值和强制转换(删除所有额外的范围和精度)之外,由具有浮动操作数的运算符产生的值和经过通常算术转换的值和浮动常量的值被评估为范围和精度可能更大的格式比类型要求的多。 使用评估格式的特点是实现定义的值
FLT_EVAL_METHOD
:-1
无法确定;0
仅根据类型的范围和精度评估所有操作和常量;1
将float
和double
类型的运算和常量评估为double
类型的范围和精度,将long double
运算和常量评估为long double
类型的范围和精度;2
将所有运算和常量评估为long double
类型的范围和精度。
FLT_EVAL_METHOD
的所有其他负值表示实现定义的行为。
(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;
英特尔 IA-32 架构 (“i386”) 上的 C 实现通常以这种方式工作:它们使用 Intel x87 浮点单元以 80 位二进制浮点算术计算表达式,精度为 64 位(“双扩展精度”),然后将结果存储在double
变量中、作为double
参数传递或显式转换为double
的任何地方舍入到IEEE 754 binary64。5
但是,ECMAScript 中不允许使用这种计算表达式的方法,因此您不必担心。
一个通过编译为 ECMAScript 来工作的 C 实现很明显,只需将 FLT_EVAL_METHOD
定义为 0
。
1 NaN 有效负载的内容可能因实现而异。 但是,是否结果是 NaN,以及 NaN 结果是信令还是静默,由标准定义。
2 一些硬件还提供非标准操作模式,例如清零,这会导致在 IEEE 754 语义下操作返回零,它们将返回次正规数;在这种情况下,硬件不是标准的实现。 如果您启用这些模式,那么您可能会得到不同的答案,但通常它们不会启用,并且它们违反了诸如Sterbenz lemma 等数值算法通常假设的定理,因此它们仅用于专门的应用程序。 ECMAScript 不支持清零或其他非标准操作模式,我所知道的任何实现也不支持:您可以依赖 IEEE 754 中定义的逐渐下溢到次规范。
3
IEEE 754 允许实现保持动态舍入模式,定义了四个舍入方向:到最近/平偶、向上(朝向正无穷大)、向下(朝向负无穷大)和朝向零。
在某些环境中,程序可以查询和更改当前的舍入模式,例如在 C 中使用 fegetround
和 fesetround
,尽管对此的工具链支持通常是有限的,它主要用于将小扰动注入数值算法以检查剧烈输出的变化表明算法存在问题。
ECMAScript 不支持更改舍入模式,我所知道的任何实现也不支持:您只需处理默认的舍入到最近/连到偶数。
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 位精度的 ulps,但仍能得到一个在 53 位精度的几个 ulps 内的答案。
【讨论】:
以上是关于IEEE 754-2008 是确定性的吗?的主要内容,如果未能解决你的问题,请参考以下文章