Number & Math in depth
Go deeper into JavaScript numbers: safe integers, numeric limits, exact checks, formatting, Math helpers, random-number traps, and modern precision tools.
- 01Know the safe zoneUse
Number.EPSILON, safe-integer checks, and the real Number limits. - 02Choose exact testsPick
Number.isNaN,Number.isFinite,Number.isInteger, andNumber.isSafeIntegerdeliberately. - 03Use Math responsiblyFormat numbers, avoid random bias, and feature-detect new Math helpers.
Precision is a map, not a mystery
The Numbers & math lesson showed the famous 0.1 + 0.2 surprise, NaN, Infinity, rounding, parsing, and the basic Math object. This lesson keeps the same one JavaScript type—number—but zooms in until you can see the ruler marks.
A JavaScript Number is a double-precision floating-point value. That means it can represent a huge range, but not every integer or decimal in that range. Near 1 the marks are extremely close together; far away they spread out. Professional number code is mostly about knowing which question you are asking: “Is this finite?”, “Is this an exact integer?”, “How should I print it?”, or “Is this random choice fair?”
Use strict Number. checks for validation, Number.isSafeInteger for exact integer identity, scaled Number.EPSILON for tiny decimal comparisons, and Math.floor for unbiased integer buckets.
You will also see recent helpers such as Math.sumPrecise and Math.f16round. They are feature-detected in the browser after mount because older engines may not have them yet.
Number.EPSILON & safe integers
STEP THROUGHNumber.EPSILON is the gap between 1 and the next representable Number. Think of it as the width of the smallest ruler mark near 1. It is perfect for explaining why 0.1 + 0.2 can be “close enough” to 0.3, but because the ruler marks grow farther apart at large magnitudes, real comparison helpers scale the tolerance by the size of the values.
Near 1, JavaScript’s number ruler has marks so close together that tiny decimal dust can be ignored. But the farther you move from zero, the wider the gaps become. Eventually you reach the last house number where every integer address still exists: Number.MAX_SAFE_INTEGER.
- In real life: A ruler with tiny marks near 1
- In JavaScript:
Number.EPSILON, the next gap after 1 - In real life: A city map that skips no houses downtown
- In JavaScript: Safe integers up to
Number.MAX_SAFE_INTEGER - In real life: A rural road where house numbers jump
- In JavaScript: Unsafe integers beyond
2 ** 53 - 1
Where the analogy stops: The ruler marks are not the same width everywhere. Number.EPSILON is a near-1 fact, so scale it for larger values.
Step through the boundary. Notice that “integer” and “safe integer” are different ideas.
Step through the boundary where integer arithmetic stops being safe, then compare a tiny decimal error with Number.EPSILON.
script
console.log(maxSafe + 1);console.log(maxSafe + 2);console.log(Number.isSafeInteger(maxSafe + 1));console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);When you truly need whole numbers beyond the safe range—large IDs, counters, or exact arithmetic—the BigInt lesson shows the separate bigint type. Do not silently store huge identifiers in Number and hope every digit survives.
MAX_VALUE, MIN_VALUE & Infinity
Safe integers answer “which whole-number addresses are reliable?” Number.MAX_VALUE and Number.MIN_VALUE answer a different question: “how large or tiny can a finite floating-point magnitude be?”
The name MIN_VALUE is the trap. It is not the most negative Number. It is the thinnest positive slice JavaScript can cut above zero: 5e-324.
- In real life: Top of the building
- In JavaScript:
Number.MAX_VALUE, the largest finite Number - In real life: A slice of paper just above the floor
- In JavaScript:
Number.MIN_VALUE, the smallest positive Number - In real life: Falling below the lobby
- In JavaScript: Negative numbers, down to
-Number.MAX_VALUE - In real life: Past the roof
- In JavaScript:
Infinityafter overflow
Where the analogy stops: Buildings have a lowest basement. Number.MIN_VALUE is not that; it is positive and extremely close to zero.
| Name | Real meaning | Use it when |
|---|---|---|
Number.MAX_SAFE_INTEGER | 9007199254740991, the last integer before gaps appear | You need exact integer identity |
Number.MAX_VALUE | Largest finite floating-point magnitude | You are guarding against overflow |
Number.MIN_VALUE | Smallest positive value above zero | You are discussing underflow or subnormal values |
Infinity / -Infinity | A Number value outside the finite range | A calculation overflowed or division by zero produced an unbounded result |
If a calculation becomes too large, JavaScript does not throw. It produces Infinity. That can be useful in algorithms, but for user-facing quantities—prices, distances, percentages—treat it as invalid data.
Number.isInteger, isSafeInteger, isNaN & isFinite
SORTJavaScript has two families of NaN and finite checks. The old global functions convert their input first. The Number. methods do not. That difference is the whole story.
| Input | isNaN | Number.isNaN | isFinite | Number.isFinite |
|---|---|---|---|---|
| "abc" | true | false | false | false |
| "" | false | false | true | false |
| "42" | false | false | true | false |
| NaN | true | true | false | false |
| Infinity | false | false | false | false |
| null | false | false | true | false |
Use global isNaN or isFinite only when you intentionally want Type conversion first. In validation code, convert explicitly with Number(input), then use strict checks. For whole numbers, decide whether you merely need “no fraction” (Number.isInteger) or a safe exact integer (Number.isSafeInteger).
isNaN("abc")isFinite("")Number.isNaN("abc")Number.isFinite("42")Number.isInteger(4.0)Number.isSafeInteger(2 ** 53)
Sort each predicate by the family it belongs to. The explanations call out coercion and safety.
toPrecision, toExponential & toString(radix)
INTERACTIVEFormatting methods answer “how should this Number be written as text?” toPrecision(5) keeps five significant digits. toExponential(2) uses scientific notation with two digits after the decimal. toString(radix) writes the same quantity in another base, from binary (2) through hexadecimal (16) up to base 36.
The car has gone the same distance no matter which odometer digits you use. Binary and hex are just compact ways to read the same quantity, especially when debugging bits, colors, and byte values.
- In real life: Decimal odometer
- In JavaScript:
255 - In real life: Binary odometer
- In JavaScript:
11111111from(255).toString(2) - In real life: Hex odometer
- In JavaScript:
fffrom(255).toString(16)
Where the analogy stops: Changing radix changes the text representation, not the stored Number. Floating-point limits still apply before you print.
Try the presets, especially 2 ** 53, 5e-324, and -0. The previous and next rows show the nearest representable neighbors: the real spacing of the Number ruler at that point.
const value = Number(input);Number.isInteger(value);Number.isSafeInteger(value);value.toPrecision(5);value.toExponential(2);value.toFixed(2);value.toString(2);value.toString(16);falsefalse123.461.23e+2123.461111011.01110100101111000110101001111110111110011101117b.74bc6a7ef9dcfalse123.45599999999999123.45600000000002123.456 is outside the safe-integer yes-zone. Decimals are stored as the nearest binary floating-point value.
Math: rounding, trigonometry, logs & hypot
STEP THROUGHThe Math object is a toolbox, not a class you instantiate. You already know basics like Math.max. Deeper work often uses four groups: rounding (round, floor, ceil, trunc), trigonometry (sin, cos, atan2), logarithms (log, log10, log2), and geometry helpers like hypot.
Trigonometry uses radians, not degrees. The helper below converts degrees to radians, then runs real Math calls. At 180°, Math.sin(Math.PI) gives 1.2246467991473532e-16, a tiny near-zero value.
Change the angle, then step through real Math calls. Watch for the almost-zero sine at 180°.
script
const radians = degrees * Math.PI / 180;console.log(Math.sin(radians));console.log(Math.hypot(3, 4));console.log(Math.log10(1000));console.log(Math.log2(8));Math.floor moves toward -Infinity, Math.ceil toward Infinity, Math.trunc toward zero, and Math.round to the nearest integer with half cases moving toward +Infinity.
Math.random pitfalls
RUN ITMath.random() returns a pseudo-random Number in the half-open range [0, 1). It is fine for games, visual variety, simulations, and demos. It is not cryptographically secure. For tokens, passwords, or hard-to-guess IDs, use Web Crypto APIs such as crypto.getRandomValues() or crypto.randomUUID(); the Web Crypto lesson covers that security mindset.
Two common bugs are biased integer generation and biased shuffling. Math.round(Math.random() * 6) gives uneven end buckets and includes 0. array.sort(() => Math.random() - 0.5) asks a sorting algorithm to use a comparator that changes its mind.
function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min;} function shuffle(items) { const copy = [...items]; for (let i = copy.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [copy[i], copy[j]] = [copy[j], copy[i]]; } return copy;}Biased round result: 0–6
0000000Fair floor result: 1–6
000000sort(() => random - 0.5)
000000Fisher–Yates
000000Click Run samples to create a real distribution in your browser. No random numbers are generated during server render.
Math.random() when you click. Do not use Math.random() for secrets.Math.sumPrecise, fround, f16round, clz32 & imul
FEATURE DETECTSome Math helpers exist for precision or low-level interop. Math.fround rounds a Number to the nearest 32-bit floating-point value; Math.fround(5.5) is 5.5, while Math.fround(5.05) is 5.050000190734863. Math.clz32(1) returns 31 because a 32-bit view of 1 has thirty-one leading zero bits. Math.imul(0xffffffff, 5) returns -5 because it multiplies as signed 32-bit integers.
Math.sumPrecise and Math.f16round are newer. This page checks for them in the browser after mount. When sumPrecise is absent, compare the documented purpose with the naive sum shown below: [1e16, 1, -1e16] loses the 1 when added naively. When f16round is absent, the page describes it as rounding to half precision rather than pretending to run it.
These helpers are specialized: float32 rounding, bit operations, and feature-detected new Math methods.
script
console.log(Math.fround(5.05));console.log(Math.clz32(1));console.log(Math.imul(0xffffffff, 5));console.log(typeof Math.sumPrecise === "function");console.log(Math.fround(5.5));console.log(Math.fround(5.05));console.log(Math.clz32(1));console.log(Math.imul(0xffffffff, 5));console.log(typeof Math.sumPrecise === "function");[1e16, 1, -1e16] sum……not available here…rounds to half precision when available5.050000190734863 is float32, not half precisionWaiting for the browser before checking optional Math methods.
Math.sumPrecise and Math.f16round are recent. Older browsers and Node 22 may not have them.Where you’ll use this
Number depth shows up in ordinary frontend work:
- Validate form input with
Number.isFiniteafter intentional conversion, so""does not sneak in as zero. - Check
Number.isSafeInteger(id)before treating numeric IDs as exact. Use BigInt or strings for identifiers beyond the safe range. - Use
toString(16)andtoString(2)when inspecting colors, flags, masks, and byte values. - Use
Math.hypot(dx, dy)for pointer distance, canvas geometry, and drag thresholds. - Use Fisher–Yates for quizzes, flashcards, and playlists; avoid random sort comparators.
value.toPrecision(5)value.toString(16)Math.ceil(price)Math.floor(Math.random() * n)- Fisher–Yates shuffle
Math.imul(hash, 31)
Sort each tool by the job it is best suited for.
Common misconceptions
“MIN_VALUE is the most negative number.”
No. It is the smallest positive Number. The most negative finite Number is -Number.MAX_VALUE.
“If a value is an integer, it is safe.”
Number.isInteger(2 ** 53) is true, but Number.isSafeInteger(2 ** 53) is false.
“Number.EPSILON fixes every decimal comparison.”
It is the gap near 1. Scale your tolerance for larger magnitudes.
“The global checks are stricter because they are shorter.”
The opposite: isNaN and isFinite coerce first. Number.isNaN and Number.isFinite are strict.
“Math.random is fair and secure for everything.”
It is neither cryptographically secure nor automatically unbiased after you transform it.
Practice: precision and Math
5 EXERCISESPredict the four console lines without running the code, then check your answer.
console.log(Number.isSafeInteger(2 ** 53));
console.log(Number.isFinite("42"));
console.log(isFinite("42"));
console.log((255).toString(16));The lines print false, false, true, and ff. The strict finite check rejects the string, the global one converts it to 42, and 255 in hexadecimal is ff.
isAlmostEqual(a, b)Write a helper that treats tiny floating-point dust as equal, then test 0.1 + 0.2 against 0.3.
function isAlmostEqual(a, b) {
return Math.abs(a - b) <= Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b));
}
console.log(isAlmostEqual(0.1 + 0.2, 0.3));function isAlmostEqual(a, b) {
return Math.abs(a - b) <= Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b));
}
console.log(isAlmostEqual(0.1 + 0.2, 0.3));The helper compares the distance with a tolerance based on the size of the inputs. It prints true for the classic decimal sum.
Fill in a helper that returns an integer from min through max, inclusive. Which rounding method belongs in it?
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}Math.floor turns [0, rangeSize) into equal integer buckets. Adding min shifts those buckets into the requested range.
Write the shuffle shown in the lesson. The important correctness property is not that one run looks mixed; it is that the result keeps the same items exactly once.
function shuffle(items) {
const copy = [...items];
for (let i = copy.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}function shuffle(items) {
const copy = [...items];
for (let i = copy.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}The loop chooses one of the remaining positions for each slot. A correct shuffle is always a permutation: it keeps the same elements exactly once.
Use toString(radix) to print 255 in binary and hexadecimal.
const value = 255;
console.log(value.toString(2));
console.log(value.toString(16));const value = 255;
console.log(value.toString(2));
console.log(value.toString(16));(255).toString(2) prints 11111111. (255).toString(16) prints ff. Both are strings representing the same Number.
Quiz: check your understanding
7 QUESTIONSEvery code answer is based on real JavaScript output. Read the explanations for the wrong answers too.
Question 1 of 7What does the unsafe integer equality print?
Read the code, then predictconsole.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);Choose an answer to see the explanation.
Question 2 of 7What is
Number.MIN_VALUE?Choose an answer to see the explanation.
Question 3 of 7What do the strict and global NaN checks print?
Read the code, then predictconsole.log(Number.isNaN("abc")); console.log(isNaN("abc"));Choose an answer to see the explanation.
Question 4 of 7What does decimal ten print in radix 2?
Read the code, then predictconsole.log((10).toString(2));Choose an answer to see the explanation.
Question 5 of 7What does the sine of JavaScript's PI print?
Read the code, then predictconsole.log(Math.sin(Math.PI));Choose an answer to see the explanation.
Question 6 of 7Which expression gives an unbiased die roll from 1 through 6?
Choose an answer to see the explanation.
Question 7 of 7What do fround, clz32, and imul print?
Read the code, then predictconsole.log(Math.fround(5.05)); console.log(Math.clz32(1)); console.log(Math.imul(0xffffffff, 5));Choose an answer to see the explanation.
Key takeaways
Number.EPSILONis the gap near 1; scale it when comparing larger values.Number.MAX_SAFE_INTEGERis about exact integer identity;Number.MAX_VALUEandNumber.MIN_VALUEare floating-point magnitude limits.- Prefer strict
Number.predicates unless you intentionally want coercion. toPrecision,toExponential, andtoString(radix)print the same Number with different rules.Math.random()is not secure, and transformations can introduce bias.- Feature-detect recent helpers such as
Math.sumPreciseandMath.f16round.
Remember the one-liner.
A JavaScript Number is a wide but spaced-out ruler: know the gap, check the range, and choose the right Math tool.
Up next: Internationalization with Intl — formatting numbers for real readers, currencies, locales, and units.