更具体地说:由于 ECMAScript 2015 指定数字值是
原始值对应于双精度64位二进制格式IEEE 754-2008值
我可以得出结论,在此处进行相同的操作将产生相同的结果,与环境无关?
(这里有很多脚注来防止那些喜欢挑剔的人,但它们不会影响您对ECMAScript的问题。)
如果我使用双精度64位IEEE 754-2008值相同的值,并执行相同的原始操作(加法,乘法,比较等),那么我是否会得到相同的结果,而与底层机器无关?
是的。
IEEE 754-2008(和IEEE 754-2019)标准精确地定义了所有浮点值上的加法,减法,乘法,除法和平方根运算,除了不同NaN值之间的区别。1 标准的实现2在所有输入上达成一致。 对于三路比较(<,=或>,定义在数字上,包括无穷大;在NaN时引发异常)或四路比较(<,=,>或无序,定义在所有浮点值包括NaN上),情况也是如此。
这五个算术运算不仅在所有输入上都有精确定义,而且对于数字输入,它们被定义为正确舍入:浮点加法运算 ⊕ 被定义为给出 fl( + ),即根据当前舍入模式3舍入实数和 + 的结果,默认返回最接近的浮点数,或者如果存在平局,则返回最后一位数字为偶数的最接近的数。
更具体地说:由于 ECMAScript 2015指定一个数字值是
与双精度 64 位二进制格式 IEEE 754-2008 值相对应的原始值
我可以得出结论,在此处执行相同操作将独立于环境产生相同的结果吗?
是的。
在ECMAScript 2015中,数字的+
、-
、*
和/
运算都与IEEE 754达成一致,对所有输入都有明确定义。4例如,ECMAScript 2015中的加法的定义明确说明:
ECMAScript 2021中的加法的定义基本相同,更新为引用IEEE 754-2019:加法的结果是根据IEEE 754-2008二进制双精度算术规则确定的:
同样地,ECMAScript 2015中的相等性和ECMAScript 2021中的相等性都是按照IEEE 754-2008和IEEE 754-2019的规定定义的,但没有明确的引用。 ECMAScript 2015中的关系运算符和ECMAScript 2021中的关系运算符都实现了IEEE 754中有序比较的概念,当输入中任一值为NaN时返回抽象操作Number::add接受参数x(一个数字)和y(一个数字)。它根据IEEE 754-2019二进制双精度算术规则执行加法,生成其参数的总和。
false
,否则返回适当的排序。
Math.sqrt
在ECMAScript 2015中,以及Math.sqrt
在ECMAScript 2021中,允许返回一个实现定义的近似值(受到有关边界情况的约束),即使IEEE 754自始至终都精确定义了平方根运算。
实际上,一个实现无法按照IEEE 754要求返回正确舍入的结果的可能性极小。
注意:很多操作除了四五个基本算术运算(+、-、*、/;Math.sqrt)之外,都是允许的,并且很可能会因实现而异。例如,一个实现可能使用简单的多项式逼近来计算Math.log1p,而另一个实现可能使用一组表驱动的逼近,对某些输入给出略微不同的结果。这有时被利用作为浏览器指纹识别的向量。但是,任何你使用基本算术运算实现的逼近在所有ECMAScript实现中都是相同的。在ECMAScript 2015中,运算符%
和ECMAScript 2021中的%
对于所有输入都有精确定义,但与IEEE 754余数操作不一致:ECMAScript %
使用截断除法,而IEEE 754余数使用最接近/平均分配除法。(在C语言中,ECMAScript的%
是fmod
,而IEEE 754余数是remainder
。)
上述答案并不总是适用于其他语言。例如,绝大多数C实现为double
提供IEEE 754二进制64位算术,为float
提供二进制32位算术,但C标准允许它们在表达式内使用不同的算术规则,并通过FLT_EVAL_METHOD
宏指定规则:
FLT_EVAL_METHOD
的实现定义值:
-1
不确定;0
仅将所有操作和常量评估到类型的范围和精度;1
将类型为 float
和 double
的操作和常量评估到 double
类型的范围和精度,将类型为 long double
的操作和常量评估到 long double
类型的范围和精度;2
将所有操作和常量评估到 long double
类型的范围和精度。FLT_EVAL_METHOD
的所有其他负值都表征着实现定义行为。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;
}
double
变量、作为double
参数传递或显式转换为double
时,将其四舍五入到IEEE 754 binary64。5
然而,在ECMAScript中不允许使用这种表达式计算方法,因此您不必担心。按照明显的编译成ECMAScript的方式实现C的实现将简单地将FLT_EVAL_METHOD
定义为0
。
1 NaN有效负载的内容因实现而异。 但是,是否为NaN以及NaN结果是信号还是静默,由标准定义。
2 一些硬件也提供了非标准的操作模式,比如flush-to-zero,当操作根据IEEE 754语义返回次正常数时,会导致操作返回零;在这种情况下,硬件不是标准的实现。 如果启用这些模式,则可能会得到不同的答案,但通常它们没有启用,并且违反了数字算法通常假定的定理,例如Sterbenz引理,因此只用于专门的应用程序。 ECMAScript不支持flush-to-zero或其他非标准的操作模式,我所知道的任何实现都不支持:您可以依赖IEEE 754中定义的渐进下溢到次正常数。
IEEE 754允许实现维护动态舍入模式,定义了四个舍入方向:最近舍入/平均舍入、向上(朝正无穷)、向下(朝负无穷)和朝零。在某些环境中,程序可以查询和更改当前的舍入模式,例如在C中使用fegetround
和fesetround
,但是对此的工具链支持通常有限,并且它主要用于将小扰动注入数值算法中,以检查输出中的急剧变化,以指示算法中的问题。ECMAScript不支持更改舍入模式,我所知道的任何实现都不支持:您只需要处理默认的最近舍入/平均舍入。