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.EPSILONto compare decimals, check whether whole numbers are exact, and know the biggest and smallest numbers. - 02Pick the right checkKnow when to use
Number.isNaN,Number.isFinite,Number.isInteger, andNumber.isSafeInteger. - 03Use Math wellShow numbers as text, make fair random choices, and check that new Math tools exist before you use them.
Which numbers JavaScript can store exactly
The Numbers & math lesson showed the basics: the 0.1 + 0.2 surprise, NaN, Infinity, rounding, reading numbers from text, and the Math object. This lesson stays with the same type, number, and looks closer.
JavaScript stores every number in the same fixed amount of memory: 64 bits. That is enough for very big and very small numbers. It is not enough to store every number exactly. So JavaScript stores the nearest number it can.
Think of all the numbers JavaScript can store as marks on a long ruler:
- Near 1, the marks are very close together.
- Far from zero, the marks are far apart.
- A number between two marks is moved to the nearest mark.
Most number bugs come from asking the wrong question. This lesson helps you ask the right one:
- Is this a real, finite number?
- Is this whole number stored exactly?
- How should I show this number as text?
- Is this random choice fair?
To check input, use the Number. checks, like Number.isFinite. To check that a whole number is exact, use Number.isSafeInteger. To compare two decimals, allow a tiny difference based on Number.EPSILON. To pick a fair random whole number, use Math.floor.
You will also meet two new tools, Math.sumPrecise and Math.f16round. Older browsers don't have them yet. So this page first checks whether your browser has them, and only then uses them.
Number.EPSILON and safe whole numbers
STEP THROUGHNumber.EPSILON is the gap between 1 and the next number JavaScript can store. It is about 0.0000000000000002. On the ruler, it is the space between the mark at 1 and the next mark.
It helps with decimals. 0.1 + 0.2 is not exactly 0.3, but the difference is smaller than Number.EPSILON. So you can say the two are "close enough". For bigger numbers the marks are farther apart, so you must make the allowed difference bigger too.
Whole numbers have a limit too. A whole number is safe when JavaScript can store it and every whole number below it exactly. The biggest safe whole number is Number.MAX_SAFE_INTEGER, which is 2 ** 53 - 1, or 9007199254740991. (** means "to the power of".)
Near 1, the marks on the ruler are so close that a tiny error doesn't matter. Far from zero, the marks spread out. It is like a street: in town, every house number exists. Out on a country road, the numbers jump, and some house numbers are missing. Number.MAX_SAFE_INTEGER is the last house where no number is missing yet.
- In real life: A ruler with tiny marks near 1
- In JavaScript:
Number.EPSILON, the gap just after 1 - In real life: A street in town where every house number exists
- In JavaScript: Safe whole numbers, up to
Number.MAX_SAFE_INTEGER - In real life: A country road where house numbers jump
- In JavaScript: Whole numbers past
2 ** 53 - 1, where some are missing
Step through the code below. Watch what happens just past the last safe whole number. A number can be whole and still not be safe.
Step past the last safe whole number. 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);maxSafe is 9007199254740991. Adding 1 gives 9007199254740992. Adding 2 should give 9007199254740993, but JavaScript can't store that whole number, so it uses the nearest one it can: 9007199254740992 again.The next code has four small checks.
const total = 0.1 + 0.2;
console.log(total === 0.3);
console.log(Math.abs(total - 0.3) < Number.EPSILON);
console.log(Math.abs(1000000000000.1 - 1000000000000) < Number.EPSILON);
console.log(Object.is(-0, 0));Line by line:
- Line 1 adds
0.1 + 0.2and keeps the result intotal. - Line 2 asks whether
totalis exactly0.3. It printsfalse, because the decimals are stored a tiny bit off. - Line 3 asks whether the difference is smaller than
Number.EPSILON. It printstrue. - Line 4 does the same for two numbers near one trillion. Their difference is about 0.1, which is far bigger than
Number.EPSILON. It printsfalse. - Line 5 compares
-0and0withObject.is. It printsfalse.
Line 4 shows that Number.EPSILON alone is too small for big numbers. For big numbers, pick an allowed difference that fits your data, like one cent for prices.
JavaScript has a minus zero, -0. -0 === 0 is true, so === can't tell them apart. Object.is(a, b) checks whether two values are exactly the same, so it can.
Sometimes you need exact whole numbers past the safe limit: long IDs, big counters, or exact sums. The BigInt lesson shows a separate type for that, bigint. Don't keep a huge ID in a normal number. Some of its digits can change without any warning.
The biggest number, the smallest number, and Infinity
Safe whole numbers answer one question: "Which whole numbers are stored exactly?" Two other limits answer a different question: "How big, or how close to zero, can a normal number get?"
Number.MAX_VALUEis the biggest number JavaScript can store. It is about1.8e308.Number.MIN_VALUEis the smallest number above zero that JavaScript can store. It is about5e-324.
The e means "times 10 to the power of". So 1.8e308 is a whole number with 309 digits, and 5e-324 is a tiny number with 323 zeros after the decimal point before the 5.
The name MIN_VALUE tricks many people. It is not the most negative number. It is the thinnest sheet you can place just above the floor, which is zero: 5e-324.
- In real life: The roof of the building
- In JavaScript:
Number.MAX_VALUE, the biggest number JavaScript can store - In real life: A thin sheet of paper just above the floor
- In JavaScript:
Number.MIN_VALUE, the smallest number above zero - In real life: The floors below the ground
- In JavaScript: Negative numbers, down to
-Number.MAX_VALUE - In real life: Past the roof
- In JavaScript:
Infinity, when a result is too big
The four names below sound alike but mean different things. Each row gives the name, what it really means, and when you need it.
| Name | What it really means | When you need it |
|---|---|---|
Number.MAX_SAFE_INTEGER | 9007199254740991. It is the last whole number before some whole numbers go missing. | You need whole numbers to be exact. |
Number.MAX_VALUE | The biggest number JavaScript can store. | You want to know when a result gets too big. |
Number.MIN_VALUE | The smallest number above zero, 5e-324. | You want to know when a result gets too close to zero to store. |
Infinity / -Infinity | A special number that is bigger, or smaller, than every normal number. | A result was too big, or you divided by zero. |
When a result is too big, JavaScript doesn't throw an error. It gives Infinity instead. Some code uses Infinity on purpose, for example as a starting value when you look for the smallest number. But for things people see, like prices, distances, or percentages, treat Infinity as wrong data.
Checking numbers: isInteger, isSafeInteger, isNaN, and isFinite
SORTJavaScript has two sets of checks for NaN ("not a number") and for finite numbers. A finite number is a normal number: not NaN, not Infinity, and not -Infinity.
- The old global checks,
isNaNandisFinite, first change the value into a number. Then they check it. - The newer checks,
Number.isNaNandNumber.isFinite, change nothing. A value that is not a number is neverNaNand never finite to them.
That one difference explains every result below.
isNaN first turns "abc" into a number. That gives NaN, so it says true. Number.isNaN changes nothing. Text is not the NaN value, so it says false.The table runs all four checks on six values. Read one row at a time: the first cell is the value, and the other cells show what each check returns.
| Value you check | 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 |
Look at the "" row. The empty string turns into 0, so the global isFinite says true. That is how an empty form field can slip through as zero.
Use the global checks only when you want that change into a number to happen. When you check user input, change it yourself with Number(input), then use the Number. checks.
There are two checks for whole numbers too:
Number.isInteger(x)asks: "Is this a number with nothing after the decimal point?"Number.isSafeInteger(x)asks: "Is this a whole number, and is it stored exactly?"
isNaN("abc")isFinite("")Number.isNaN("abc")Number.isFinite("42")Number.isInteger(4.0)Number.isSafeInteger(2 ** 53)
Put each check in its group. Does it change the value into a number first, change nothing, or check for whole numbers?
Showing numbers as text: toPrecision, toExponential, and toString
INTERACTIVEThese methods answer one question: "How should this number look as text?" Each one returns a string.
toPrecision(5)keeps 5 significant digits. Those are the digits that count, starting from the first digit that is not zero.(123.456).toPrecision(5)gives"123.46".toExponential(2)writes the number with ane, keeping 2 digits after the decimal point.(123.456).toExponential(2)gives"1.23e+2", which means 1.23 times 10 to the power of 2.toString(radix)writes the number in another base. The base, also called the radix, is how many different digits you use. We normally use base 10: the digits 0 to 9. Base 2 (binary) uses only 0 and 1. Base 16 (hexadecimal, or "hex") uses 0 to 9 and then a to f. Any base from 2 to 36 works.
A car has a dial, called an odometer, that counts how far the car has gone. Imagine three cars that drove the same distance, each with a different dial. The digits look different, but the distance is the same. Binary and hex are just other ways to write the same number. They are handy when you look at colors, on/off flags, and bytes.
- In real life: A dial with the digits 0 to 9
- In JavaScript:
255 - In real life: A dial with only 0 and 1
- In JavaScript:
11111111from(255).toString(2) - In real life: A dial with 0 to 9 and a to f
- In JavaScript:
fffrom(255).toString(16)
console.log(parseInt("1010", 2));
console.log((255).toString(2));
console.log((1234.5).toFixed(2));
console.log((1234.5).toPrecision(4));Line by line:
- Line 1 reads the text
"1010"as base 2. The second argument,2, is the base. It prints10. - Line 2 goes the other way. It writes 255 in base 2 and prints
11111111. - Line 3 keeps exactly 2 digits after the decimal point. It prints
1234.50. - Line 4 keeps 4 significant digits. It rounds 1234.5 to
1235.
All of these give back a string. Use them to show a number to people. Don't do more math on the result.
Now try the number inspector. Type a number or click a preset, especially 2 ** 53, 5e-324, and -0. The "number just below" and "number just above" rows show the closest numbers JavaScript can store on each side. The gap between them is the space between the marks on the ruler at that spot.
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 not a safe whole number. This number has a decimal part. JavaScript stores the nearest number it can.
Math tools: rounding, angles, logarithms, and distance
STEP THROUGHMath is a ready-made toolbox of number functions. You don't create it with new; you just call its functions, like Math.max. This section uses four groups of tools:
- Rounding:
Math.round,Math.floor,Math.ceil, andMath.trunc. - Angles:
Math.sin,Math.cos, andMath.atan2. This part of math is called trigonometry. - Logarithms:
Math.log10(x)asks "10 to what power gives x?", andMath.log2(x)asks the same for 2.Math.loguses the number e (about 2.718). - Distance:
Math.hypot(a, b)gives the length of the long side of a right triangle with sides a and b.
The angle functions don't use degrees. They use radians, another way to measure angles. 180 degrees is π radians. The code below changes degrees into radians, then calls real Math functions. At 180 degrees, Math.sin(Math.PI) gives 1.2246467991473532e-16. That is not zero, but it is very close. It happens because Math.PI is stored a tiny bit off.
Change the angle, then step through real Math calls. Look at the sine at 180 degrees: it is almost 0, but not quite.
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));Each one turns a number into a whole number:
Math.flooralways goes down, toward-Infinity.Math.ceilalways goes up, towardInfinity.Math.trunccuts off the part after the decimal point, so it goes toward zero.Math.roundgoes to the nearest whole number. Exactly half, like.5, goes up, towardInfinity.
console.log(Math.round(2.5));
console.log(Math.round(-2.5));Line 1 prints 3. Line 2 prints -2, not -3. Going up from -2.5 means going toward the bigger number, and -2 is bigger than -3. So Math.round does not always round away from zero.
Math.random: when random is not fair
RUN ITMath.random() gives a random-looking number from 0 up to 1. It can be 0, but it is never 1. It is fine for games, changing how a page looks, simulations, and demos.
It is not safe for secrets. Someone could guess the numbers it makes. For tokens, passwords, or IDs that must be hard to guess, use the browser's Web Crypto tools, such as crypto.getRandomValues() or crypto.randomUUID(). The Web Crypto lesson covers them.
Two common bugs make random results unfair:
Math.round(Math.random() * 6)for a die roll. It can give 0, and it gives 0 and 6 only half as often as the other results.array.sort(() => Math.random() - 0.5)to shuffle.sortexpects its compare function to give the same answer for the same two items every time. A random answer breaks that, so some orders come up more often than others.
r is Math.random(). A wider card means a more likely result. Rounding gives seven results, and the two end ones get only half a share. Math.floor plus 1 gives six equal shares.The fair way to shuffle is the Fisher–Yates shuffle. It walks from the last item to the first. For each spot, it swaps in a random item from the ones not placed yet. Every order is equally likely.
Click the button below to make real random samples in your browser. Compare the unfair and fair versions.
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;}Unfair: Math.round gives 0 to 6
0000000Fair: Math.floor gives 1 to 6
000000Unfair shuffle: sort(() => random - 0.5)
000000Fair shuffle: Fisher–Yates
000000Click the button to make real random samples in your browser. Nothing random happens until you click.
Math.random() when you click. Don't use Math.random() for secrets.Special Math tools: sumPrecise, fround, f16round, clz32, and imul
STEP THROUGHSome Math tools exist for special jobs: very exact sums, or working with other systems that store numbers in fewer bits. A bit is one binary digit, 0 or 1. A normal JavaScript number uses 64 bits. These tools work with 32 bits:
Math.fround(x)rounds x to the nearest number that fits in 32 bits.Math.fround(5.5)stays5.5, butMath.fround(5.05)becomes5.050000190734863.Math.clz32(x)counts the zeros at the start of x, written as 32 binary digits.Math.clz32(1)gives31: thirty-one zeros, then a single 1.Math.imul(a, b)multiplies two whole numbers the way a 32-bit system does. In that system,0xffffffffmeans -1, soMath.imul(0xffffffff, 5)gives-5.
Math.sumPrecise and Math.f16round are newer, and some browsers don't have them yet. So this page waits until it is running in your browser, then checks whether they exist. It only calls them if they do.
Math.sumPreciseadds a list of numbers exactly. Adding[1e16, 1, -1e16]one by one gives0: the 1 is lost.Math.sumPrecisegives1.Math.f16roundrounds a number to fit in 16 bits. If your browser doesn't have it, the page only describes it.
The bitwise operators, like | and &, work on single bits. Before they start, they turn each side into a 32-bit whole number. That can change big numbers.
console.log((2 ** 32 + 1) | 0);This prints 1. 32 bits can hold only 2 ** 32 different whole numbers. Past that, the count starts again from zero. So 2 ** 32 + 1 becomes just 1. This is useful for flags and quick hash codes, but don't use it to work with big whole numbers.
These tools have special jobs: rounding to 32 bits, working with bits, and checking that a new Math tool exists.
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] one by one……not in this browser…rounds to 16 bits when the browser has it5.050000190734863 is 5.05 rounded to 32 bits, not 16Waiting for the page to start in your browser before checking.
Math.sumPrecise and Math.f16round are new. Older browsers and Node 22 may not have them.Where you’ll use this
These tools show up in everyday web work:
- Check form input: change it with
Number(input), then useNumber.isFinite. That way an empty field ("") doesn't slip through as zero. - Check
Number.isSafeInteger(id)before you trust a number ID to be exact. Keep bigger IDs as strings or as BigInt. - Use
toString(16)andtoString(2)to look at colors, on/off flags, and bytes. - Use
Math.hypot(dx, dy)to find how far the mouse moved, for drawing on a canvas, or to decide when a drag starts. - Use the Fisher–Yates shuffle for quizzes, flashcards, and playlists. Don't shuffle with a random
sort.
value.toPrecision(5)value.toString(16)Math.ceil(price)Math.floor(Math.random() * n)- Fisher–Yates shuffle
Math.imul(hash, 31)
Put each tool in the group for the job it does best.
Common misconceptions
"MIN_VALUE is the most negative number."
No. It is the smallest number above zero. The most negative normal number is -Number.MAX_VALUE.
"If a number is whole, it is safe."
No. Number.isInteger(2 ** 53) is true, but Number.isSafeInteger(2 ** 53) is false. Past the safe limit, some whole numbers can't be stored.
"Number.EPSILON fixes every decimal comparison."
No. It is the gap next to 1. For bigger numbers the gaps are bigger, so the allowed difference must grow too.
"The global checks are stricter because they are shorter."
It is the other way round. isNaN and isFinite change the value into a number first. Number.isNaN and Number.isFinite change nothing, so they are stricter.
"Math.random is fair and safe for everything."
No. It is not safe for secrets, because its numbers can be guessed. And the way you turn it into a whole number can make some results more likely than others.
Practice: exact numbers and Math
5 EXERCISESGuess the four printed 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. 2 ** 53 is not safe. Number.isFinite says no to a string. The global isFinite changes "42" into 42 first, so it says yes. And 255 in base 16 is ff.
isAlmostEqual(a, b)Write a function that says two numbers are equal when they differ only by a tiny error. Test it with 0.1 + 0.2 and 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 function compares the distance between the numbers with an allowed difference. That allowed difference grows with the size of the numbers. For 0.1 + 0.2 and 0.3, it prints true.
Complete a function that returns a whole number from min to max, with both ends included. Which rounding tool 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 cuts the range into equal slices, one for each whole number. Adding min moves those slices so they start at min.
Write the shuffle from this lesson. One run looking mixed is not enough. The result must keep the same items, each 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;
}For each spot, from the end, the loop picks one of the items not placed yet. The result always has the same items, each exactly once, in a new order. That is called a permutation.
Use toString(radix) to print 255 in base 2 and in base 16.
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 for the same number.
Quiz: check your understanding
7 QUESTIONSEvery code answer comes from real JavaScript output. Read the explanations for the wrong answers too.
Question 1 of 7What does this line print, just past the safe limit?
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 two 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 ten print in base 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
Math.PIprint?Read the code, then predictconsole.log(Math.sin(Math.PI));Choose an answer to see the explanation.
Question 6 of 7Which line gives a fair die roll from 1 to 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 next to 1. When you compare bigger numbers, make the allowed difference bigger too.Number.MAX_SAFE_INTEGERis about whole numbers being exact.Number.MAX_VALUEandNumber.MIN_VALUEare about how big, or how close to zero, a number can get.- Use the
Number.checks. Use the global checks only when you want the value changed into a number first. toPrecision,toExponential, andtoString(radix)write the same number as text in different ways.Math.random()is not safe for secrets. Round its result the wrong way and some results come up more often.- Check that new tools like
Math.sumPreciseandMath.f16roundexist before you call them.
Remember the one-liner.
JavaScript numbers are marks on a long ruler with gaps: know the gap, check the limits, and pick the right Math tool.
Up next: Internationalization with Intl. It shows numbers the way people in each country expect: money, languages, and units.