3 Numbers
JavaScript numbers look simple on the surface, but the chapter shows that they rest on a surprisingly intricate foundation. The language has only two numeric types, Number and BigInt, and the familiar problem of 0.1 + 0.2 exposing 0.30000000000000004 comes from IEEE 754 floating-point encoding in binary rather than from a flaw in JavaScript itself. To explain this, the text walks through how values are turned into bits, how binary and two’s complement represent integers, and how floating-point uses a sign, exponent, and significand to cover a huge range of values while sacrificing exact decimal precision.
The chapter also digs into the practical consequences of that design. It explains why floating-point values are only approximate, how rounding errors accumulate, and why Number.EPSILON can help compare numbers that are “close enough” while still needing to be scaled for the magnitude of the values involved. It highlights special cases such as Infinity, negative infinity, and NaN, and shows where Number reaches its limits, especially around the maximum safe integer, beyond which some integers cannot be represented exactly. Because of these limits, the text positions BigInt as the solution for exact whole-number arithmetic when values exceed the safe range or when correctness matters more than speed.
Finally, the chapter looks at how JavaScript engines optimize numeric performance behind the scenes, especially in V8. Small integers can be stored as SMIs, avoiding heap allocation and making common arithmetic faster, while larger or non-integer values become HeapNumbers. The discussion emphasizes that consistency matters: keeping loop counters and array indices as integers, avoiding unnecessary floating-point math, and not mixing numeric representations can help the engine stay on its fast path. The chapter closes by pointing toward a proposed Decimal type, designed to provide exact base-10 arithmetic for financial and other decimal-heavy work, giving JavaScript a more complete numeric toolbox for the future.
Unsigned and signed binary layout consists of any number of bits. In the unsigned variant, every bit is used to represent the value, whereas in the signed variant, the most significant bit is reserved as representing the sign and the remaining bits represent the value.
The “double-precision” floating point layout divides the 64 available bits into distinct components, including a sign-bit, eleven exponent bits, and 52 bits for the significand.
Summary
- Numbers are 64-bit floating-point values (Binary64) that cannot precisely represent decimal fractions like 0.1 or 0.2.
- Binary encoding causes 0.1 + 0.2 to equal 0.30000000000000004 due to infinite repeating patterns in base-2 representation.
- 2's complement encoding enables signed integers by inverting bits and adding 1, allowing the same arithmetic circuits for positive and negative numbers.
- Floating-point trades range for precision: 53 bits of accuracy regardless of magnitude, from 5e-324 to 1.7976931348623157e+308.
- The maximum safe integer is 2^53 - 1 (9,007,199,254,740,991), beyond which not all integers can be represented exactly.
- Number.EPSILON provides the smallest representable difference for comparing floating-point values that should be equal but aren't due to rounding.
- BigInt uses dynamic memory allocation to represent arbitrarily large integers without precision loss, at 10-200x performance cost.
- JavaScript forbids mixing BigInt and Number in arithmetic to prevent accidental precision loss, though comparisons work.
- The proposed Decimal type will use base-10 encoding to enable exact decimal arithmetic for financial and scientific calculations.
- V8 optimizes integers between -2^31 and 2^31-1 as SMIs, storing them directly without heap allocation for better performance.
- Consistent numeric types in arrays and loops help V8 maintain optimizations, while mixing types forces slower generic handling.
JavaScript in Depth ebook for free