Why JavaScript Float Addition Is Inaccurate: Inside V8’s Machine Code
This article explains how the V8 JavaScript engine generates machine code for floating‑point addition on Intel x64, covering V8’s architecture, the IEEE‑754 representation that causes precision loss, and a step‑by‑step analysis of the relevant C++ source and the resulting addsd instruction.
Abstract
This article introduces how the V8 engine generates machine code for floating‑point addition on the Intel x64 platform. It first gives a brief overview of V8, then explains why floating‑point addition results are inaccurate from C and x64 assembly perspectives, and finally examines the V8 source code that produces the corresponding machine code.
V8 Overview
V8 is Google’s open‑source high‑performance JavaScript and WebAssembly engine written in C++. It powers Chrome, Node.js, and many other applications, running on Windows, macOS, and Linux across x64, IA‑32, ARM, and MIPS architectures. The source tree contains many directories; the core JavaScript‑related code resides in the
srcfolder.
The compilation pipeline is: the parser converts JavaScript to an AST (in
src/parsingand
src/ast), the interpreter generates bytecode (
src/interpreter), and the optimizing compiler later translates hot bytecode into machine code (
src/compilerand
src/codegen).
Why JavaScript Float Operations Are Inaccurate
Floating‑point numbers follow the IEEE‑754 double‑precision format (64 bits: 1 sign bit, 11 exponent bits, 52 mantissa bits). Because the mantissa is limited, adding numbers with vastly different exponents can lose low‑order bits, causing results like
a == dto be true even though mathematically
a < d.
<code>#include <stdio.h>
#include <math.h>
int main() {
double a = pow(2, 100);
double b = pow(2, 47);
double c = pow(2, 48);
double d = a + b;
double e = a + c;
printf("a == d is %d\n", a == d);
printf("a == e is %d\n", a == e);
}
</code>On x86, the exponent alignment shifts the smaller operand, and the extra mantissa bit is discarded, leading to the observed equality.
How V8 Generates Float‑Addition Machine Code
On x64, the floating‑point addition instruction is
addsd(or
vaddsd). V8 emits this instruction via the
Assembler::addsdmethod, which writes the opcode bytes
0xF2 0x0F 0x58and then calls
emit_sse_operandto produce the ModR/M byte
0xC8for
addsd xmm1, xmm0.
<code>void Assembler::addsd(XMMRegister dst, XMMRegister src) {
EnsureSpace ensure_space(this);
emit(0xF2);
emit_optional_rex_32(dst, src);
emit(0x0F);
emit(0x58);
emit_sse_operand(dst, src);
}
</code>The
emit_sse_operandmethod constructs the final byte by combining the high bits
0xC0with the low‑order bits of the destination and source registers:
<code>void Assembler::emit_sse_operand(XMMRegister dst, XMMRegister src) {
emit(0xC0 | (dst.low_bits() << 3) | src.low_bits());
}
</code>For
dst = xmm1(code 1) and
src = xmm0(code 0), the calculation yields
0xC0 | 0x08 | 0x00 = 0xC8, completing the four‑byte machine code
F2 0F 58 C8for the addition.
Conclusion
The V8 engine translates JavaScript floating‑point addition into the
addsdinstruction by emitting the exact opcode bytes defined by the Intel manual. Understanding this process clarifies why floating‑point precision issues arise and how V8’s code generator implements the operation at the machine‑code level.
Weidian Tech Team
The Weidian Technology Platform is an open hub for consolidating technical knowledge. Guided by a spirit of sharing, we publish diverse tech insights and experiences to grow and look ahead together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.