by Christoph Michel

# 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)`

.

Indeed this is what the specification says about `Math.pow`

:

Return the result of Applying the ** operator with base and exponent as specified in 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`

.

However, evaluating the following in the current V8 JS Engine (Chrome / Node) results in this:

`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:

`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`

.

So what seems to be happening here is that the compiler processes the JS code `1.35 ** 92`

which is already constant folded

This makes sense as V8 really compiles to machine code.

**V8 increases performance by compiling JavaScript to native machine code before executing it, versus executing bytecode or interpreting it.**

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.

TurboFan now does **constant folding.** Its exponentiation algorithm has a better precision than the JIT compiler’s (Ignition) exponentiation algorithm.

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.

#### 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.

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.

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 😉):

`function isChrome() { return 1.35 ** 92 !== Math.pow(1.35, 92)}`

Still more readable than user agent strings. 🤷

Originally published at cmichel.io