幂运算运算符
by Christoph Michel
克里斯托夫·米歇尔(Christoph Michel)
我在V8的幂运算符中发现了一个错误 (I found a bug in V8’s Exponentiation Operator)
I always thought that the new ES6 exponentiation operator x ** y
was the same as Math.pow(x,y)
.
我一直认为新的ES6指数运算符x ** y
与Math.pow(x,y)
。
Indeed this is what the specification says about Math.pow
:
实际上,这就是规范对Math.pow
:
Return the result of Applying the ** operator with base and exponent as specified in 12.6.4.
返回将12.6.4中指定的带有基数和指数的**运算符应用的结果。
12.6.4 — Applying the ** Operator states that the result is implementation-dependent — but there should still be no discrepancy between **
and Math.pow
.
12.6.4 — 应用**运算符表示结果取决于实现 ,但**
与Math.pow
之间仍然应该没有差异。
However, evaluating the following in the current V8 JS Engine (Chrome / Node) results in this:
但是,在当前的V8 JS引擎(Chrome / Node)中评估以下内容会导致:
console.log('1.35 ** 92', 1.35 ** 92) // 978828715394.7672console.log('Math.pow(1.35, 92)', Math.pow(1.35, 92)) // 978828715394.767
The exponentiation operator **
returns a more accurate approximation.
求幂运算符**
返回更精确的近似值。
But this is not the only weirdness with the exponentiation operator: Let’s try evaluating the same with variables (REPL) — it shouldn’t make any difference:
但这不是幂运算符的唯一怪异:让我们尝试使用变量( REPL )进行评估—它不会有任何区别:
const exponent = 92;console.log(`1.35 ** exponent`, 1.35 ** exponent) // 978828715394.767console.log('1.35 ** 92', 1.35 ** 92) // 978828715394.7672console.log(`Math.pow(1.35, exponent)`, Math.pow(1.35, exponent)) // 978828715394.767console.log('Math.pow(1.35, 92)', Math.pow(1.35, 92)) // 978828715394.767
But it does: 1.35 ** 92
differs from 1.35 ** exponent
.
但确实如此: 1.35 ** 92
与1.35 ** exponent
不同。
So what seems to be happening here is that the compiler processes the JS code 1.35 ** 92
which is already constant folded
因此,这里似乎正在发生的事情是,编译器处理了已经固定折叠的JS代码1.35 ** 92
This makes sense as V8 really compiles to machine code.
由于V8确实可以编译为机器代码,因此这很有意义。
V8 increases performance by compiling JavaScript to native machine code before executing it, versus executing bytecode or interpreting it.
V8通过在执行之前将JavaScript编译为本地机器代码,而不是执行字节码或解释它来提高性能。
V8 works by first interpreting the JS code with their Ignition Interpreter. It does a second run with the TurboFan compiler optimizing the machine code.
V8的工作方式是首先使用其点火解释器解释JS代码。 它使用TurboFan编译器进行第二次运行,以优化机器代码 。
TurboFan now does constant folding. Its exponentiation algorithm has a better precision than the JIT compiler’s (Ignition) exponentiation algorithm.
TurboFan现在可以不断折叠。 它的幂运算算法比JIT编译器的(Ignition)幂运算算法具有更高的精度。
If you try the same in other JS engines like Firefox’s SpiderMonkey, the result is a consistent value of 978828715394.767
among all computations.
如果您在其他JS引擎(例如Firefox的SpiderMonkey)中尝试相同的操作,则结果是所有计算中的值始终为978828715394.767
。
是虫子吗? (Is it a bug?)
I would say so, although it wasn’t severe in my code. But it’s still not following the spec that says Math.pow
and **
should result in the same value.
我会这么说,尽管在我的代码中并不严格。 但是它仍然没有遵循说Math.pow
和**
应该产生相同值的规范。
If you’re transpiling the code with babel, x ** y
is translated to Math.pow(x,y)
, which again leads to discrepancies between transpiled and untranspiled code. As we have seen, Math.pow(1.35, 92)
is not being optimized (only operators seem to be optimized by V8). Therefore, 1.35 ** 92
results in different code when transpiled to ES5.
如果您使用babel编译代码,则x ** y
会转换为Math.pow(x,y)
,这又会导致已编译和未编译的代码之间出现差异。 如我们所见, Math.pow(1.35, 92)
尚未优化(似乎只有V8对运算符进行了优化)。 因此,当转换为ES5时, 1.35 ** 92
会生成不同的代码。
Using this bug and disregarding any clean code practices, we can write a nice function to determine if we’re running on Chrome (unless you transpile your code ?):
使用此错误并忽略任何干净的代码实践,我们可以编写一个不错的函数来确定我们是否在Chrome上运行(除非您将代码转译为?):
function isChrome() { return 1.35 ** 92 !== Math.pow(1.35, 92)}
Still more readable than user agent strings. ?
比用户代理字符串更具可读性。 ?
Originally published at cmichel.io
最初发布于cmichel.io
翻译自: https://www.freecodecamp.org/news/i-found-a-bug-in-v8s-exponentiation-operator-dcddfa5b8482/
幂运算运算符