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 surprisesUse IEEE 754 intuition and
Number.EPSILONfor tiny floating-point errors. - 02Handle special valuesRecognize
NaN,Infinity, and-0without being fooled by coercion. - 03Use number toolsRound, parse, call
Math, and write readable numeric literals.
Numbers are one type
JavaScript has one everyday number type: number. It holds whole numbers like 7, decimals like 3.14, and a few special values you will meet soon. The Data types lesson already gave away one odd fact: typeof NaN is "number". This lesson explains why that is not a typo.
Numbers show up everywhere: prices, animation positions, form inputs, page counts, timers, chart values, and random choices. Most of the time they feel like calculator numbers. But JavaScript stores them as 64-bit floating-point values, so some decimals are tiny approximations instead of exact values.
A measuring cup has printed marks. If the amount you want lands between two marks, you choose the nearest one. JavaScript numbers work similarly: they choose the nearest representable value.
- In real life: A cup can show 1/2 exactly
- In JavaScript: Binary can store
0.5exactly - In real life: A cup may need a mark between printed ticks
- In JavaScript: Some decimals land between representable Numbers
- In real life: You round for the recipe
- In JavaScript: You round for display with
toFixedor compare with a tolerance
Where the analogy stops: A measuring cup is visibly imprecise. JavaScript's approximations are usually so tiny that you only notice them in equality checks or many repeated calculations.
We will keep arithmetic operators like +, -, *, /, %, and precedence light here because the next module starts with Arithmetic & assignment. We will also only tease BigInt, Type conversion, Number & Math in depth, and Internationalization with Intl; each gets its own lesson later.
Floating point: why 0.1 + 0.2 is not exactly 0.3
INTERACTIVEJavaScript numbers follow the IEEE 754 double-precision format. A friendly way to picture it is: one bit for the sign, some bits for the size of the number, and the rest for the significant digits. You do not need to memorize the layout yet. The important idea is that there are many numbers, but only finitely many bit patterns, so some values are represented by their nearest neighbor.
In decimal, 1 / 3 becomes 0.3333… forever. You must stop writing somewhere, so a tiny error remains. Binary has the same problem with values like 0.1.
- In real life: Decimal cannot finish
1/3:0.3333… - In JavaScript: Binary cannot finish
0.1exactly - In real life: You stop writing after enough digits
- In JavaScript: IEEE 754 stores a nearest finite bit pattern
- In real life: The rounded value is close enough for many recipes
- In JavaScript: The stored value is close enough for most UI work
Where the analogy stops: This analogy is about representation, not base-ten display. JavaScript still prints friendly decimal strings most of the time so you are not staring at the stored bits.
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 real sum is extremely close, but not exactly equal. Use a tolerance or round for display instead of expecting exact decimal equality.
0.1 + 0.2 case. The controls run the same checks with other decimal pairs.The surprise is not that JavaScript is bad at math. It is that base 2 and base 10 have different easy fractions. 0.5 and 0.25 are exact because they are halves and quarters. 0.1 is not exact, so 0.1 + 0.2 lands at the nearest representable number, 0.30000000000000004.
Whole numbers are exact up to Number.MAX_SAFE_INTEGER, which is 9007199254740991. Above that, JavaScript cannot represent every neighboring integer. The BigInt lesson will show the n suffix for integers beyond that limit.
- 0.5 + 0.25
- 0.1 + 0.2
- 2 ** 53 + 1
- 1000000 * 3
- 0.1 * 3
- 9007199254740991
Sort each expression by whether JavaScript can represent the result exactly as a Number in normal use.
NaN, Infinity & -0
STEP THROUGHNumbers include a few values that are not ordinary points on a number line. NaN means “not a valid numeric answer.” Infinity and -Infinity are what you get when a calculation overflows the normal range or divides by zero. Negative zero is a real sign bit attached to zero.
Imagine a clerk stamping “not a valid answer” on a broken form. Any later calculation that uses that form is still tainted.
- In real life: A form stamped invalid is not a usable answer
- In JavaScript:
NaNis not a usable number - In real life: Copies of the bad form stay bad
- In JavaScript: Most calculations with
NaNproduceNaN - In real life: You need a special inspection
- In JavaScript: Use
Number.isNaN(value)
Where the analogy stops: NaN is still a JavaScript Number value, not an error object. Code keeps running unless you check for it.
Step through the strange-looking values. Each line was recorded from real JavaScript, including the coercing isNaN example.
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);| Value | How it appears | What to remember |
|---|---|---|
NaN | 0 / 0, failed numeric parsing | It is not equal to itself. Prefer Number.isNaN over old coercing isNaN. |
Infinity | 1 / 0, very large overflow | It is still a number value. It can move through later calculations. |
-Infinity | -1 / 0, 1 / -0 | The sign matters when direction matters. |
-0 | 0 * -1, rounding a tiny negative value | -0 === 0 is true, but Object.is(-0, 0) is false. |
Think of Infinity as an odometer that has run out of digits: it is not a useful street address, but it tells you which direction the calculation went. Think of -0 like a thermometer showing -0° after rounding a tiny negative temperature.
Rounding & toFixed
STEP THROUGHJavaScript gives you several rounding tools, and their differences are clearest with negative numbers. Math.round goes to the nearest integer, with halves moving toward +Infinity. Math.floor moves down, Math.ceil moves up, and Math.trunc simply drops the fractional part.
Pick a value, predict each rounded result, then step through the functions 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));| Expression | Result | Why |
|---|---|---|
Math.round(2.5) | 3 | Nearest integer; half moves toward +Infinity. |
Math.round(-2.5) | -2 | The same tie rule moves upward toward +Infinity. |
Math.floor(-2.5) | -3 | Floor means down the number line. |
Math.trunc(-2.5) | -2 | Truncation removes the decimal part, toward zero. |
(1.005).toFixed(2) | "1.00" | The stored value is a hair below 1.005. |
(2.345).toFixed(2) | "2.35" | This stored value rounds to 2.35 in current JavaScript engines. |
For prices, count in cents as integers: store 1299 instead of 12.99. Format for humans at the edge with (cents / 100).toFixed(2). The later Internationalization with Intl lesson shows proper currency formatting.
parseInt & parseFloat
INTERACTIVEForm inputs and URL pieces often arrive as text. parseInt reads an integer from the start of a string. parseFloat reads a decimal number from the start. Number(...), which the Type conversion lesson covers in depth, tries to convert the whole value.
const input = "42px";console.log(parseInt(input, 10));console.log(parseFloat(input));console.log(Number(input));4242NaNparseInt and parseFloat scan from the start and stop when the text no longer fits. Number tries to convert the whole string, so it is stricter except for cases like the empty string.
| Expression | Result | Reason |
|---|---|---|
parseInt("42px", 10) | 42 | Reads digits, then stops at p. |
parseInt("px42", 10) | NaN | The string does not start with a number. |
parseInt("3.99", 10) | 3 | Integers stop before the decimal point. |
parseInt("101", 2) | 5 | Radix 2 means binary: 4 + 1. |
parseInt("0x1F") | 31 | The 0x prefix means hexadecimal. |
parseFloat("3.14abc") | 3.14 | Reads the decimal prefix and stops at a. |
Number("42px") | NaN | The whole string is not a number. |
Number("") | 0 | One of conversion's surprising rules. |
parseInt(0.0000005, 10) | 5 | The number becomes the string "5e-7", so parsing stops at e. |
Use parseInt(text, 10) when you mean decimal. Radix makes your intent obvious, and it lets you parse other bases on purpose, like parseInt("101", 2).
The Math object
INTERACTIVEMath is a built-in object full of number helpers. You do not create it; you call its functions directly. Common ones include Math.max, Math.min, Math.abs, Math.pow, Math.sqrt, Math.PI, and Math.random.
| Tool | Example | Result |
|---|---|---|
| Largest | Math.max(4, 9, 2) | 9 |
| Smallest | Math.min(4, 9, 2) | 2 |
| No max arguments | Math.max() | -Infinity |
| Absolute value | Math.abs(-8) | 8 |
| Power | Math.pow(2, 5) | 32 |
| Square root | Math.sqrt(49) | 7 |
| Circle constant | Math.PI.toFixed(3) | "3.142" |
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.
rollDie helper many times to prove it stays in range.The dice formula works because Math.random() is at least 0 and less than 1. Multiplying by 6 gives a decimal from 0 up to but not including 6. Math.floor turns that into 0 through 5, and + 1 shifts the range to 1 through 6.
Numeric separators
Long numbers are hard to read. Numeric separators let you put underscores between digits: 1_000_000 is the same value as 1000000. They are for humans only; JavaScript removes them while reading the literal.
const population = 1_000_000;const cents = 12_50;console.log(population === 1000000);console.log(cents);| Allowed? | Example | Why |
|---|---|---|
| Yes | 1_000_000 | Between digits. |
| Yes | 12_50 | Sometimes used for cents-like groups, though 1_250 is clearer for thousands. |
| No | _1000 | Not at the start. |
| No | 1000_ | Not at the end. |
| No | 1__000 | Not doubled. |
| No | 1._5 | Not immediately after the decimal point. |
Where you’ll use this
Number work appears whenever a page turns user input into a decision. A shopping cart parses quantities, keeps money in cents, rolls random recommendations, and rounds only when showing a value to a person.
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 is fine for displaying with toFixed(2), but it fails exact equality. The second example keeps the total as the integer number of cents until the final display step.
Common misconceptions
“JavaScript has separate integer and decimal types.”
Every ordinary numeric value is a number. Integers are exact only inside the safe range.
“If a number prints as 0.3, it must equal 0.3.”
Printing often chooses a friendly short decimal. Equality checks compare the stored value.
“toFixed gives me a number.”
toFixed returns a string, because its job is display formatting.
“parseInt is just Number for whole numbers.”
parseInt scans a prefix. Number converts the whole string and has different empty-string rules.
“NaN means JavaScript threw an error.”
NaN is a value. It can spread through later arithmetic unless you check for it.
Practice: numbers in the wild
5 EXERCISESPredict all three printed lines before checking.
console.log(0.1 + 0.2);
console.log(Number.isNaN(0 / 0));
console.log(Object.is(-0, 0));The lines print 0.30000000000000004, true, and false. The sum is approximate, 0 / 0 is really NaN, and Object.is(-0, 0) is false.
The buggy code displays fine but fails equality. What unit should the program store internally?
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 + 20 cents as integers. The equality check is exact, then (cents / 100).toFixed(2) creates display text: 0.30.
Read the function. What is the smallest and largest possible return value?
function rollDie() {
return Math.floor(Math.random() * 6) + 1;
}function rollDie() {
return Math.floor(Math.random() * 6) + 1;
}Math.floor(Math.random() * 6) creates integers 0 through 5. Adding 1 gives the die faces 1 through 6.
Parse "12.5kg" so the decimal part is kept.
const input = "12.5kg";
console.log(parseFloat(input));const input = "12.5kg";
console.log(parseFloat(input));parseFloat reads the numeric prefix 12.5 and stops at k. parseInt would lose the .5, and Number would reject the kg.
Use the helper to round 1.005 to cents. 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 helper shifts the decimal two places, uses Math.round, then shifts back. Number.EPSILON nudges tiny floating-point representation errors in the intended direction for this beginner helper.
Quiz: check your understanding
7 QUESTIONSPick an answer, then read every explanation. The wrong answers are designed to catch real beginner mistakes.
Question 1 of 7What does the decimal sum code print?
Read the code, then predictconsole.log(0.1 + 0.2);Choose an answer to see the explanation.
Question 2 of 7What does the 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 7Which expression returns the number
42from"42px"?Read the code, then predictconsole.log(parseInt("42px", 10));Choose an answer to see the explanation.
Question 5 of 7What can
Math.max()return when called with no arguments?Read the code, then predictconsole.log(Math.max());Choose an answer to see the explanation.
Question 6 of 7Which numeric literal is valid?
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
- JavaScript’s everyday number type is a 64-bit IEEE 754 floating-point value.
- Some decimals, including
0.1, are stored as tiny approximations. NaN,Infinity, and-0are real Number values with special rules.toFixedreturns display text; keep money as integer cents internally.parseIntandparseFloatscan prefixes;Numberconverts the whole value.Mathgives you rounding, powers, square roots, constants, and random decimals.
Remember the one-liner.
JavaScript numbers are fast, useful approximations for decimals, exact for safe integers, and full of built-in helpers when you know the edge cases.
Up next: Strings.