1import{t as e}from"./chunk-BFmYbdsN2.js";var o={id:`numbers`,moduleId:`values-types`,trackId:`core-js`,title:`Numbers, floats & BigInt`,blurb:`IEEE-754, 0.1 + 0.2, safe integers, and when to reach for BigInt.`,minutes:10,level:`working`,tldr:{model:`One number type: a 64-bit IEEE-754 double with 53 bits of precision. Every surprise falls out of the sign, exponent and significand fields.`,gotcha:"`0.1` and `0.2` are repeating binary fractions, so they are already rounded before you add them â `0.1 + 0.2` is `0.30000000000000004`."},blocks:[{kind:`prose`,md:`Every JavaScript \`number\` is a **64-bit IEEE-754 double**. There is no int type. One value, 64 bits: 1 sign, 11 exponent, 52 stored significand bits â 53 bits of precision once you count the implicit leading 1. 2 3Almost every number surprise in the language falls out of those three fields.`},{kind:`heading`,text:`0.1 + 0.2, properly explained`},{kind:`code`,lang:`js`,caption:`The classic, plus the one that ruins price totals`,code:`console.log(0.1 + 0.2); 4console.log(0.1 + 0.2 === 0.3); 5console.log((0.1 + 0.2).toFixed(20)); 6console.log((0.1 + 0.7) * 10);`,output:`0.30000000000000004 7false 80.30000000000000004441 97.999999999999999`},{kind:`prose`,md:'The usual telling â "floats are imprecise" â is too vague to be useful. The precise version: a double stores a value as `significand à 2^exponent`. Any fraction whose denominator is a power of two (`0.5`, `0.25`, `0.125`) is exact. `0.1` is `1/10`, and 10 has a factor of 5, so in binary it is a **repeating fraction** â exactly the way `1/3` repeats in decimal.\n\nSo `0.1` is already rounded before you do anything with it, and so is `0.2`. The addition is performed correctly; it is the inputs that were approximations. Note that the result still *prints* as `0.1` on its own â JavaScript prints the shortest decimal that round-trips to the same double, which hides the error until two of them combine.'},{kind:`code`,lang:`js`,caption:`Comparing floats without lying to yourself`,code:`console.log(Number.EPSILON); 10console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); 11 12// scaled, for values not near 1 13const close = (a, b) => 14 Math.abs(a - b) <= Number.EPSILON * Math.max(Math.abs(a), Math.abs(b), 1); 15console.log(close(1e9 + 0.1 + 0.2, 1e9 + 0.3));`,output:`2.220446049250313e-16 16true 17true`},{kind:`callout`,tone:`warn`,title:`Never store money as a number`,md:"Store **integer minor units** â cents, satoshi, basis points â and divide only at the moment you render, with `Intl.NumberFormat`. Integer arithmetic below 2^53 is exact, so a cents-based total never drifts. The proposed `Decimal` type is still a proposal; do not wait for it."},{kind:`heading`,text:`The safe integer range`},{kind:`code`,lang:`js`,caption:`Where the integers run out`,code:`const M = Number.MAX_SAFE_INTEGER; 18console.log(M); 19console.log(M + 1, M + 2); 20console.log(M + 1 === M + 2); 21console.log(Number.isSafeInteger(2 ** 53));`,output:`9007199254740991 229007199254740992 9007199254740992 23true 24false`},{kind:`prose`,md:`With 53 bits of precision, every integer up to 2^53 - 1 has an exact representation. Past that, the spacing between representable doubles becomes 2, then 4, then 8 â so odd integers simply stop existing and two different sums land on the same value. 25 26This is why a 64-bit database id, a Twitter/X snowflake id or a nanosecond timestamp must cross the wire as a **string** or a **BigInt**. \`JSON.parse\` will happily mangle it otherwise, silently, and only for some ids.`},{kind:`heading`,text:`NaN and negative zero`},{kind:`code`,lang:`js`,caption:`Two values that break equality in opposite directions`,code:`console.log(NaN === NaN, Object.is(NaN, NaN)); 27console.log([NaN].indexOf(NaN), [NaN].includes(NaN)); 28 29console.log(0 === -0, Object.is(0, -0)); 30console.log(1 / -0, String(-0));`,output:`false true 31-1 true 32true false 33-Infinity 0`},{kind:`prose`,md:"`NaN` is the only value not equal to itself â that is IEEE-754, not a JavaScript quirk. Detect it with `Number.isNaN` (which never coerces) rather than the legacy global `isNaN` (which does: `isNaN('abc')` is `true` because the string converts to NaN first).\n\n`-0` is the mirror problem: it *is* strictly equal to `0`, and `String(-0)` is `'0'`, so it hides. It surfaces when you divide (`1 / -0` is `-Infinity`) or sort, and it is a genuine bug source in geometry and animation code.\n\nThree equality algorithms, worth naming: **`===`** (NaN unequal, zeroes equal), **SameValueZero** used by `includes`, `Map` and `Set` (NaN equal, zeroes equal), and **SameValue** used by `Object.is` (NaN equal, zeroes different)."},{kind:`heading`,text:`BigInt`},{kind:`code`,lang:`js`,caption:`Arbitrary precision, deliberately unfriendly`,code:`console.log(10n + 20n, typeof 10n); 34console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n); 35console.log(1n == 1, 1n === 1, 2n > 1); 36console.log(5n / 2n); 37try { console.log(1n + 1); } catch (e) { console.log(e.constructor.name); }`,output:`30n bigint 389007199254740993n 39true false true 402n 41TypeError`},{kind:`prose`,md:"BigInt is an arbitrary-precision **integer** type. It **compares** across types (`1n == 1`, `2n > 1`) but refuses to do **arithmetic** across them â mixing throws a `TypeError`. That is intentional: a silent BigInt-to-double conversion would reintroduce the precision loss the type exists to prevent.\n\nDivision truncates, because there are no fractional BigInts. And `JSON.stringify` throws on one, so serialising means an explicit `.toString()` and a matching `BigInt()` on the way back in."},{kind:`compare`,aLabel:`number`,bLabel:`BigInt`,rows:[{label:`Representation`,a:`64-bit IEEE-754 double`,b:`Arbitrary-precision integer`},{label:`Exact integers up to`,a:`2 to the 53, minus 1`,b:`Unbounded, memory permitting`},{label:`Fractions`,a:`Yes, approximately`,b:`No â division truncates`},{label:`Mixing in arithmetic`,a:`Coerces freely`,b:`Throws a TypeError`},{label:`JSON`,a:`Native`,b:`Throws â serialise as a string`},{label:`Speed`,a:`Hardware fast`,b:`Noticeably slower`}]},{kind:`callout`,tone:`gotcha`,title:`toFixed is not the rounding you expect`,md:"`(1.
41005).toFixed(2)` gives `'1.00'` and `(1.115).toFixed(2)` gives `'1.11'`. Neither is a rounding bug â those literals are stored *just below* the decimal you typed, so rounding down is correct. It is another symptom of the same cause, and another reason to round integer cents instead.\n\nTwo more conversion traps in the same family: `Number('')` and `Number(' ')` are both `0`, not `NaN`; and `parseInt('0x10')` is `16`, because parseInt still sniffs the hex prefix."},{kind:`callout`,tone:`interview`,title:`How this gets asked`,md:'`0.1 + 0.2` is the single most asked JavaScript question there is. "Floats are imprecise" is the answer everyone gives. Say instead: *one number type, IEEE-754 double, 53 bits of precision; 0.1 is a repeating binary fraction so the literals are already rounded; compare with a scaled epsilon and store money as integer cents.* Then the follow-up â MAX_SAFE_INTEGER, or NaN not equalling itself â is already set up for you.'},{kind:`takeaways`,items:[`One number type: 64-bit IEEE-754 double, 53 bits of precision, no integers.`,`0.1 and 0.2 are repeating binary fractions, so they are rounded before you add them.`,`Compare floats with a scaled Number.EPSILON tolerance, never with ===.`,`Integers are exact only up to 2**53 - 1; beyond it, odd numbers stop existing.`,`Send 64-bit ids over the wire as strings or BigInt, never as JSON numbers.`,`NaN !== NaN; use Number.isNaN. -0 === 0 but Object.is tells them apart.`,`BigInt compares across types but throws on mixed arithmetic, and JSON cannot hold it.`,`Money is integer minor units, formatted with Intl.NumberFormat at render time.`]}],cards:e};export{o as NUMBERS};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.