Numbers & math
Learn how JavaScript represents integers and decimals, why 0.1 + 0.2 is not exactly 0.3, and how to use rounding, parsing, Math, and numeric separators safely.
- 01Explain decimal surprisesSay why
0.1 + 0.2is a tiny bit off, and check whether two numbers are close enough withNumber.EPSILON. - 02Handle special valuesSpot
NaN,Infinity, and-0, and check forNaNthe right way. - 03Use number toolsRound numbers, read numbers out of text, use
Math, and write long numbers with underscores.
Watch the video lecture
Full course on YouTube (opens in a new tab) ↗- Lecture 13Numbers in JavaScriptWatch on YouTube (opens in a new tab) ↗
One type for all numbers
In JavaScript, every everyday number has the same type: number. A type is the kind of a value. The number type holds:
- whole numbers, like
7. Whole numbers are also called integers. - decimals, like
3.14. - a few special values, like
NaN. You'll meet them later in this lesson.
The Data types lesson showed one odd fact: typeof NaN gives "number". That is not a mistake. This lesson explains why.
Web pages use numbers all the time: prices, form inputs, page counts, timers, chart values, and random picks. Most of the time, numbers act like the numbers on a calculator. But some decimals are not stored exactly. JavaScript stores a number that is very, very close instead. For example, 0.1 is stored as a number a tiny bit away from 0.1.
A measuring cup has printed marks, like 1/4, 1/2, and 3/4. Sometimes the amount you want falls between two marks. Then you pick the nearest mark.
- In real life: A cup has a printed mark for 1/2
- In JavaScript: JavaScript can store
0.5exactly - In real life: Some amounts fall between two marks
- In JavaScript: Some decimals, like
0.1, fall between numbers JavaScript can store - In real life: You pick the nearest mark
- In JavaScript: JavaScript keeps the closest number it can store
- In real life: You round the amount when you write the recipe
- In JavaScript: You round with
toFixedbefore you show a number to a person
This lesson uses the math signs +, -, *, /, and % only a little. The Arithmetic & assignment lesson, at the start of the next module, covers them. Four more number topics get their own lessons later:
- BigInt, for whole numbers too big for
number. - Type conversion, for turning other values into numbers.
- Number & Math in depth, for more number tools.
- Internationalization with Intl, for showing numbers in other languages and currencies.
Why 0.1 + 0.2 is not exactly 0.3
INTERACTIVEA computer stores everything as bits. A bit is a single 0 or 1. JavaScript stores each number in 64 bits. The rules for using those 64 bits come from a standard called IEEE 754. A standard is a shared rulebook, so every computer stores numbers the same way. Numbers stored like this are called floating-point numbers.
The 64 bits are split into three parts. You don't need to learn them yet:
- 1 bit says plus or minus.
- 11 bits say how big the number is.
- 52 bits hold the digits.
64 bits give a huge number of patterns, but not an endless number. There are endless decimals, so many of them can't be stored exactly. For those, JavaScript keeps the closest number it can store.
We count in base 10, with the digits 0 to 9. Write 1 ÷ 3 in base 10 and you get 0.3333…. The 3s never end. You have to stop somewhere, so a tiny error is left.
Computers count in base 2, called binary. Binary uses only the digits 0 and 1. In binary, 0.1 never ends either.
- In real life: In base 10,
1/3never ends:0.3333… - In JavaScript: In binary,
0.1never ends either - In real life: You stop writing after some digits
- In JavaScript: JavaScript stops after 64 bits and keeps the closest number
- In real life:
0.3333is close enough for most uses - In JavaScript: The stored number is close enough for most web pages
const left = 0.1;const right = 0.2;const sum = left + right;const expected = 0.3;console.log(sum);console.log(sum === expected);console.log(sum.toFixed(2));console.log(sum.toPrecision(20));console.log(Math.abs(sum - expected) < Number.EPSILON);0.300000000000000040.3false0.300.30000000000000004441trueThe stored sum is very close to the sum you expect, but not equal. To show it to people, round it. To compare it, allow a tiny difference instead of using ===.
0.1 + 0.2 case. Pick two other numbers to run the same checks on them.Here is the code in the playground, line by line. Lines 1 and 2 save 0.1 and 0.2, and line 3 adds them. Line 4 saves the sum you expect, 0.3. Then:
- Line 5 prints the stored sum:
0.30000000000000004. - Line 6 checks whether the sum equals
0.3. It prints false. - Line 7 uses
toFixed(2). It rounds the number to two digits after the decimal point, for showing to people. It prints0.30. - Line 8 uses
toPrecision(20)to show 20 digits. Now you can see the tiny error:0.30000000000000004441. - Line 9 checks whether the sum and
0.3are close enough.Math.absgives the gap between them, without a minus sign.Number.EPSILONis a tiny number, about 0.0000000000000002. The gap is smaller than that, so it prints true.
JavaScript is not bad at math. Base 2 and base 10 just have different easy fractions:
0.5(a half) and0.25(a quarter) are exact in binary.0.1(a tenth) is not exact in binary.
So 0.1 + 0.2 adds two numbers that are each a tiny bit off. The result is the closest number JavaScript can store: 0.30000000000000004.
0.5 + 0.25 is exactly 0.75. 0.1 and 0.2 are each stored a tiny bit off, so their sum is a tiny bit off too.Whole numbers are exact up to Number.MAX_SAFE_INTEGER, which is 9007199254740991. The whole numbers up to that limit are called safe integers. Above it, JavaScript can't store every whole number, so some get skipped. The BigInt lesson shows how to write bigger whole numbers with an n at the end, like 10n.
const biggestSafeAge = Number.MAX_SAFE_INTEGER;console.log(Number.isSafeInteger(biggestSafeAge));console.log(Number.isSafeInteger(biggestSafeAge + 1));console.log(biggestSafeAge + 1 === biggestSafeAge + 2);Line 1 saves the biggest safe whole number in biggestSafeAge. Line 2 asks Number.isSafeInteger whether it is safe, and prints true. Line 3 asks about the next whole number up, and prints false.
Line 4 shows why the limit matters. It prints true: JavaScript stores biggestSafeAge + 1 and biggestSafeAge + 2 as the same number. Use Number.isSafeInteger when a whole number must stay exact.
- 0.5 + 0.25
- 0.1 + 0.2
- 2 ** 53 + 1
- 1000000 * 3
- 0.1 * 3
- 9007199254740991
Sort each expression: can JavaScript store the result exactly, or is it a tiny bit off or unsafe?
NaN, Infinity & -0
STEP THROUGHThe number type also has a few special values. They are not ordinary numbers on a number line:
NaNstands for "Not a Number". It means "this math has no number answer", like0 / 0. Even so, its type isnumber.Infinitymeans bigger than any number JavaScript can store.-Infinitymeans smaller than any number. You get them when you divide by zero, or when a result grows too big.-0is zero with a minus sign. JavaScript keeps the sign, even for zero.
Picture a clerk who stamps "invalid" on a broken form. Any copy of that form is invalid too. So is anything worked out from it.
- In real life: A form stamped invalid can't be used
- In JavaScript:
NaNis not a usable number - In real life: Copies of the bad form are bad too
- In JavaScript: Most math with
NaNgivesNaN - In real life: You need a special check to spot the stamp
- In JavaScript: Use
Number.isNaN(value)
NaN has one more odd rule. It is not equal to anything, not even itself. So NaN === NaN is false, and === can't find it. To check for it, use Number.isNaN(value). It gives true only when the value really is NaN.
NaN is not equal to anything, not even itself, so === can't find it. Number.isNaN asks the question directly and gives true.Step through the odd-looking values one line at a time. Each result comes from running real JavaScript.
script
console.log(NaN === NaN);console.log(Number.isNaN(NaN));console.log(isNaN("hello"));console.log(Number.isNaN("hello"));console.log(1 / 0);console.log(-1 / 0);console.log(Object.is(-0, 0));console.log(1 / -0);Press Step to go through the nine lines. Lines 1 to 3 are about NaN. Lines 4 and 5 compare two checks:
- The old
isNaNfirst turns its input into a number. The text"hello"becomesNaN, so it says true. Number.isNaNdoesn't turn anything into a number. Text is notNaN, so it says false.
Lines 6 and 7 divide by zero. Lines 8 and 9 are about -0. Object.is is a function that checks whether two values are exactly the same. It can tell -0 from 0, so line 8 prints false.
The table below sums up the four special values. Read each row as: this value, how you get it, and what to remember about it.
| Value | How do you get it? | What should you remember? |
|---|---|---|
NaN | From 0 / 0, or from text that can't become a number. | It is not equal to itself. Check it with Number.isNaN, not the old isNaN. |
Infinity | From 1 / 0, or from a result too big to store. | It is still a number. Later math can use it. |
-Infinity | From -1 / 0 or 1 / -0. | The minus sign tells you which way the result went. |
-0 | From 0 * -1, or from rounding a tiny negative number. | -0 === 0 is true, but Object.is(-0, 0) is false. |
Most of the time you can treat -0 just like 0. It shows up in rare cases, like 1 / -0. Think of a thermometer that rounds -0.2° and shows -0°. The minus tells you it was just below zero.
Rounding & toFixed
STEP THROUGHRounding means turning a number into a simpler one, often a whole number. The Math object, which comes with JavaScript, has four rounding tools:
Math.roundgoes to the nearest whole number. A half, like 2.5, goes up, toward the bigger number.Math.flooralways goes down. Think of the floor of a room.Math.ceilalways goes up. "Ceil" is short for ceiling.Math.trunccuts off the decimal part. "Trunc" is short for truncate, which means cut short.
The differences show best with negative numbers. Remember that -2 is bigger than -3, because -2 is closer to zero.
-2.5 into a whole number in its own way. Math.floor always goes down, so it gives -3. Math.round sends a half up, toward the bigger number, so it gives -2.Pick a value, guess each result, then step through the tools one at a time.
script
console.log(Math.round(value));console.log(Math.floor(value));console.log(Math.ceil(value));console.log(Math.trunc(value));console.log(value.toFixed(2));Pick a value and press Step. Line 1 saves the value. Lines 2 to 5 use the four tools. Line 6 uses toFixed(2), which rounds to two digits after the decimal point. toFixed gives back text (a string), not a number, because its job is to show a number to people.
The table below shows results that often surprise people. Read each row as: this code gives this result, for this reason.
| Code | What does it give? | Why? |
|---|---|---|
Math.round(2.5) | 3 | It goes to the nearest whole number, and a half goes up. |
Math.round(-2.5) | -2 | A half still goes up, toward the bigger number. -2 is bigger than -3. |
Math.floor(-2.5) | -3 | Floor always goes down, and -3 is below -2.5. |
Math.trunc(-2.5) | -2 | Trunc cuts off the .5, which leaves -2. |
(1.005).toFixed(2) | "1.00" | 1.005 is stored a tiny bit below 1.005, so it rounds down. |
(2.345).toFixed(2) | "2.35" | 2.345 is stored a tiny bit above 2.345, so it rounds up. |
For prices, count whole cents instead of dollars. Store 1299 instead of 12.99. Whole numbers are exact, so sums stay exact. Turn cents into dollars only when you show the price to a person: (cents / 100).toFixed(2). The Internationalization with Intl lesson later shows the full way to show money.
const cents = 1299;const price = cents / 100;console.log(price.toFixed(2));console.log(price.toLocaleString("en-US", { style: "currency", currency: "USD" }));Line 1 stores the price as 1299 cents. Line 2 divides by 100 to get dollars: 12.99. Line 3 prints the text 12.99, with two digits after the point.
Line 4 uses toLocaleString. It writes a number the way people in one place expect to read it. "en-US" means English in the United States, and the options ask for US dollars. It prints $12.99. To learn more ways to show money, dates, and languages, go on to Internationalization with Intl.
parseInt & parseFloat
INTERACTIVEForm inputs and web addresses give you text, even when the text looks like a number. You need to turn the text into a number before you can do math with it. There are three tools for that:
parseInt(text, 10)reads a whole number from the start of the text. It stops at the first character that doesn't fit.parseFloat(text)reads a decimal number from the start of the text. It stops the same way.Number(text)tries to turn the whole text into a number. If any part doesn't fit, it givesNaN. The Type conversion lesson covers it in depth.
parseInt reads digits from the start of the text and stops at the first letter. Number looks at the whole text. px is not part of a number, so it gives NaN.const input = "42px";console.log(parseInt(input, 10));console.log(parseFloat(input));console.log(Number(input));4242NaNparseInt and parseFloat read from the start of the text. They stop at the first character that doesn't fit. Number needs the whole text to be a number, so it is stricter. One surprise: empty text gives 0.
The table below shows common cases. Read each row as: this code gives this result, for this reason.
| Code | What does it give? | Why? |
|---|---|---|
parseInt("42px", 10) | 42 | It reads 4 and 2, then stops at p. |
parseInt("px42", 10) | NaN | The text doesn't start with a digit. |
parseInt("3.99", 10) | 3 | It reads whole numbers only, so it stops at the decimal point. |
parseInt("101", 2) | 5 | Base 2 means binary. Binary 101 is 4 + 0 + 1, which is 5. |
parseInt("0x1F") | 31 | Text that starts with 0x is read in base 16, called hexadecimal. 1F is 16 + 15, which is 31. |
parseFloat("3.14abc") | 3.14 | It reads the decimal at the start and stops at a. |
Number("42px") | NaN | The whole text is not a number. |
Number("") | 0 | Empty text becomes 0. This is one of the surprising rules of Number. |
parseInt(0.0000005, 10) | 5 | The number is first turned into the text "5e-7", a short way to write 0.0000005. Reading stops at e. |
The second input to parseInt is the radix: the base to read the digits in. Use parseInt(text, 10) for everyday numbers. Base 10 is the usual way of counting, with the digits 0 to 9. Writing the 10 makes your plan clear. You can also pick another base on purpose, like parseInt("101", 2) for binary.
const priceText = "12.50";const ageText = "12 years";const scoreText = "98.5 points";console.log(Number(priceText));console.log(parseInt(ageText, 10));console.log(parseFloat(scoreText));Lines 1 to 3 save three pieces of text. Then each line uses a different tool:
- Line 4 uses
Number, because the whole price text is a number. It prints 12.5. - Line 5 uses
parseInt, because a word comes after the age. It prints 12. - Line 6 uses
parseFloat, to keep the decimal part of the score. It prints 98.5.
Use Number when the whole text should be a number. Use the parse tools when a unit, like px or kg, comes after the number. But be careful: they ignore everything after the number. So they can hide a typo, like "12abc", which parseInt reads as 12.
The Math object
INTERACTIVEMath is an object that comes with JavaScript, full of number tools. You don't create it. You call its functions directly, like Math.max(4, 9).
The table below shows the most useful ones. Read each row as: what the tool does, an example, and what the example gives.
| What does it do? | Example | What does it give? |
|---|---|---|
| It finds the largest number. | Math.max(4, 9, 2) | 9 |
| It finds the smallest number. | Math.min(4, 9, 2) | 2 |
| It finds the largest of no numbers at all. | Math.max() | -Infinity |
| It gives the distance from 0, without a minus sign. | Math.abs(-8) | 8 |
| It multiplies a number by itself: 2 × 2 × 2 × 2 × 2. | Math.pow(2, 5) | 32 |
| It finds the square root: the number that, times itself, gives 49. | Math.sqrt(49) | 7 |
| It gives pi, the number used for circles. | Math.PI.toFixed(3) | "3.142" |
Math.max() with no numbers gives -Infinity. It starts from the smallest value there is and keeps any bigger number it sees. With no numbers, nothing is bigger, so -Infinity is left.
Math.random() gives a random decimal. It is at least 0 and always less than 1. The playground below uses it to roll a die.
function rollDie() { return Math.floor(Math.random() * 6) + 1;}console.log(rollDie());No rolls yet.
Click Roll once. Math.random() gives a decimal from 0 up to, but not including, 1.
Here is how line 2 of the dice code works, step by step:
Math.random()gives a decimal from 0 up to, but not including, 1.* 6stretches it to a decimal from 0 up to, but not including, 6.Math.floorrounds it down to a whole number: 0, 1, 2, 3, 4, or 5.+ 1moves the range to 1 through 6, like a real die.
Underscores in numbers
Long numbers are hard to read. Is 1000000 one million or ten million? You can put underscores between the digits: 1_000_000. These underscores are called numeric separators. They are only for people who read the code. JavaScript ignores them, so 1_000_000 is the same number as 1000000.
const population = 1_000_000;const cents = 12_50;console.log(population === 1000000);console.log(cents);Line 1 stores one million, written with underscores. Line 2 stores 1250, written as 12_50. Line 3 prints true: the underscores didn't change the value. Line 4 prints 1250, with no underscore.
Underscores have a few rules. Read each row of the table as: is this allowed, and why?
| Is it allowed? | Example | Why? |
|---|---|---|
| Yes | 1_000_000 | The underscores sit between digits. |
| Yes | 12_50 | Any two digits can have an underscore between them. For thousands, 1_250 is clearer. |
| No | _1000 | A number can't start with an underscore. |
| No | 1000_ | A number can't end with an underscore. |
| No | 1__000 | You can't put two underscores in a row. |
| No | 1._5 | An underscore can't come right after the decimal point. |
Where you'll use this
Numbers show up whenever a page turns what someone typed into a decision. A shopping cart, for example:
- reads how many items you want from the text you typed.
- keeps prices in whole cents.
- picks random products to suggest.
- rounds a price only when it shows the price to you.
Compare two ways to add 10 cents and 20 cents:
const total = 0.1 + 0.2;console.log(total === 0.3);console.log(total.toFixed(2));const cents = 10 + 20;console.log(cents === 30);console.log((cents / 100).toFixed(2));The first example keeps dollars as decimals. Line 1 adds 0.1 and 0.2. Line 2 prints false, because the sum is a tiny bit off. Line 3 prints 0.30, so the screen looks fine, but the check failed.
The second example keeps whole cents. Line 1 adds 10 and 20, which is exactly 30. Line 2 prints true. Line 3 turns cents into dollar text only at the end, and prints 0.30.
Common mix-ups
Here are five common wrong ideas about numbers, and what is really true.
"JavaScript has one type for whole numbers and another for decimals."
No. Every everyday number has the type number. Whole numbers are exact only up to the safe limit.
"If a number shows as 0.30 on screen, it equals 0.3."
Not always. toFixed(2) shows 0.30 for a sum that is really 0.30000000000000004. A check with === uses the stored number, not the text on screen.
"toFixed gives me a number."
No. toFixed gives back text (a string), because its job is to show a number to people.
"parseInt is just Number for whole numbers."
No. parseInt reads only the start of the text. Number looks at the whole text, and its rules differ: Number("") gives 0, but parseInt("", 10) gives NaN.
"NaN means JavaScript stopped with an error."
No. NaN is a value, not an error. Your code keeps running, and NaN spreads through later math unless you check for it.
Practice exercises
5 EXERCISESTry each task before you look at the hints. The first ones ask what code prints. The later ones ask you to fix money, read a die, read a weight, and round to cents.
Guess all three printed lines before you check.
console.log(0.1 + 0.2);
console.log(Number.isNaN(0 / 0));
console.log(Object.is(-0, 0));It prints 0.30000000000000004, true, and false. The sum is a tiny bit off. 0 / 0 really is NaN. Object.is(-0, 0) is false, because Object.is sees the minus sign on zero.
The buggy code looks right on screen, but its check fails. What unit should the program store instead of dollars?
const total = 0.1 + 0.2;
console.log(total === 0.3);
console.log(total.toFixed(2));const cents = 10 + 20;
console.log(cents === 30);
console.log((cents / 100).toFixed(2));Store 10 cents and 20 cents as whole numbers. 10 + 20 is exactly 30, so the check is true. Then (cents / 100).toFixed(2) makes the text 0.30 to show.
Read the function. What is the smallest number it can return, and the largest?
function rollDie() {
return Math.floor(Math.random() * 6) + 1;
}function rollDie() {
return Math.floor(Math.random() * 6) + 1;
}Math.floor(Math.random() * 6) gives a whole number from 0 to 5. Adding 1 gives 1 to 6, the faces of a die.
Read the number out of "12.5kg" and keep the decimal part. What does the code print?
const input = "12.5kg";
console.log(parseFloat(input));const input = "12.5kg";
console.log(parseFloat(input));parseFloat reads 12.5 from the start and stops at k. parseInt would drop the .5. Number would give NaN, because kg is not part of a number.
Use the roundToCents function to round 1.005 to two decimals. What does it print?
function roundToCents(value) {
return Math.round((value + Number.EPSILON) * 100) / 100;
}
console.log(roundToCents(1.005));function roundToCents(value) {
return Math.round((value + Number.EPSILON) * 100) / 100;
}
console.log(roundToCents(1.005));The roundToCents function multiplies by 100, rounds with Math.round, then divides by 100. 1.005 is stored a tiny bit below 1.005. Adding Number.EPSILON first nudges it up, so it rounds to 1.01. This simple function is fine for small numbers like this one.
Check your understanding
7 QUESTIONSPick an answer, then read every explanation. The wrong answers are mistakes that beginners often make. For a code question, work out the exact value inside console.log.
Question 1 of 7What does this code print?
Read the code, then predictconsole.log(0.1 + 0.2);Choose an answer to see the explanation.
Question 2 of 7What does this NaN check print?
Read the code, then predictconsole.log(Number.isNaN(NaN));Choose an answer to see the explanation.
Question 3 of 7What does this print?
Read the code, then predictconsole.log(Math.round(-2.5));Choose an answer to see the explanation.
Question 4 of 7What does
parseIntgive for the text"42px"?Read the code, then predictconsole.log(parseInt("42px", 10));Choose an answer to see the explanation.
Question 5 of 7What does
Math.max()give when you pass it no numbers?Read the code, then predictconsole.log(Math.max());Choose an answer to see the explanation.
Question 6 of 7Which number is written correctly?
Choose an answer to see the explanation.
Question 7 of 7What is
typeof NaN?Read the code, then predictconsole.log(typeof NaN);Choose an answer to see the explanation.
Key takeaways
- Every everyday number in JavaScript has the type
number. It is stored in 64 bits, using the IEEE 754 rules. - Some decimals, like
0.1, are stored as a number that is a tiny bit off. NaN,Infinity, and-0are real number values with special rules. Check forNaNwithNumber.isNaN.toFixedgives text for people to read. Keep money in whole cents.parseIntandparseFloatread a number from the start of the text.Numberlooks at the whole text.Mathgives you rounding, powers, square roots, pi, and random numbers.
Remember the one-liner.
JavaScript numbers are exact for whole numbers up to a limit, very close for most decimals, and come with many ready-made tools.
Up next: Strings.