Prices, scores, percentages and measurements are all numbers, and JavaScript handles them with a single type. It works well most of the time, but decimals and text input have some sharp edges. This lesson shows how numbers behave and how to parse, round and calculate without surprises.
One number type
JavaScript uses one number type for whole numbers and decimals. Under the hood, every number is a 64-bit floating-point value, the same format used by most programming languages for decimals.
console.log(typeof 42);
// number
console.log(typeof 3.14);
// number
console.log(10 / 4);
// 2.5
You can write large numbers with underscores to make them readable. The underscores are ignored:
const population = 67_000_000;
console.log(population);
// 67000000
Why 0.1 + 0.2 is not 0.3
Try this in the console:
console.log(0.1 + 0.2);
// 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);
// false
This is not a JavaScript bug. Computers store numbers in binary, and some decimal fractions have no exact binary form, just as 1/3 has no exact decimal form (0.3333…). 0.1 and 0.2 are each stored as the nearest possible binary value, and the tiny errors add up.
Three practical ways to deal with it:
Round when you display. Most of the time the error only matters when someone sees it:
const total = 0.1 + 0.2;
console.log(total.toFixed(2));
// 0.30
Work in whole units for money. Store pence or cents as integers, and convert only for display. Whole numbers up to about 9 quadrillion are exact:
const pricePence = 1999;
const quantity = 3;
const totalPence = pricePence * quantity;
console.log(`£${(totalPence / 100).toFixed(2)}`);
// £59.97
Compare with a tolerance. If you must compare decimals, check whether they are close enough:
const isClose = Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;
console.log(isClose);
// true
Number.EPSILON is a very small number, about 2.2e-16. It suits values close to 1; for larger values, use a tolerance that makes sense for your data, such as 0.001.
Safe integers
Whole numbers are exact up to Number.MAX_SAFE_INTEGER, which is 9,007,199,254,740,991. Beyond that, numbers lose precision:
console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991
console.log(9007199254740992 === 9007199254740993);
// true
You will rarely hit this limit, but it matters for things like large database IDs. Such IDs are usually kept as strings, or as bigint values for calculations.
Infinity and NaN
Dividing by zero gives Infinity rather than an error. Maths that makes no sense gives NaN:
console.log(1 / 0);
// Infinity
console.log(0 / 0);
// NaN
console.log(Math.sqrt(-1));
// NaN
NaN spreads: any arithmetic involving NaN gives NaN. If a calculation shows NaN, trace back to find where a non-number got in, very often an unconverted or empty input.
Check results with Number.isNaN() and Number.isFinite():
console.log(Number.isNaN(0 / 0));
// true
console.log(Number.isFinite(1 / 0));
// false
console.log(Number.isFinite(42));
// true
Use these Number. versions rather than the older global isNaN() and isFinite(), which convert their argument first and give confusing answers such as isNaN("hello") being true.
Turning text into numbers
Text from forms, URLs and files arrives as strings. There are three common ways to convert it.
Number() converts the whole string, and gives NaN if any of it is not a number:
console.log(Number("42"));
// 42
console.log(Number("3.5"));
// 3.5
console.log(Number(" 7 "));
// 7
console.log(Number("42px"));
// NaN
console.log(Number(""));
// 0
Note that an empty string becomes 0, not NaN. Check for empty input separately.
Number.parseInt() reads a whole number from the start of a string and stops at the first character it cannot use. Always pass the base, 10, as the second argument:
console.log(Number.parseInt("42px", 10));
// 42
console.log(Number.parseInt("3.99", 10));
// 3
console.log(Number.parseInt("abc", 10));
// NaN
Number.parseFloat() does the same but keeps the decimal part:
console.log(Number.parseFloat("3.99 GBP"));
// 3.99
Which to use? Number() is the strictest, which is usually what you want for validating input: "42px" is not a valid quantity. Use parseInt and parseFloat when you know a number is followed by other text, such as a CSS value. The global parseInt and parseFloat work identically to the Number. versions.
Number inputs give you a shortcut. input.valueAsNumber returns the value of an input type="number" as a number, or NaN if it is empty or invalid.
Rounding
The Math object holds maths functions and constants. You never create it; you just call its methods.
console.log(Math.round(2.5));
// 3
console.log(Math.round(2.4));
// 2
console.log(Math.floor(2.9));
// 2
console.log(Math.ceil(2.1));
// 3
console.log(Math.trunc(-2.9));
// -2
Math.round()rounds to the nearest whole number, with halves going up.Math.floor()always rounds down;Math.ceil()always rounds up.Math.trunc()removes the decimal part, which differs fromfloor()for negative numbers.
Watch out for negative halves: Math.round(-2.5) is -2, because halves round towards positive infinity.
To round to a number of decimal places for display, use toFixed(). It returns a string:
const average = 7.4567;
console.log(average.toFixed(1));
// 7.5
console.log(typeof average.toFixed(1));
// string
Because toFixed() returns a string, use it at the last moment, when you show the value, not in the middle of calculations. For locale-aware formatting with thousands separators and currency symbols, use Intl.NumberFormat, covered in lesson 27.
Other useful Math methods
console.log(Math.max(4, 9, 2));
// 9
console.log(Math.min(4, 9, 2));
// 2
console.log(Math.abs(-7));
// 7
console.log(Math.sqrt(16));
// 4
console.log(Math.PI);
// 3.141592653589793
A common pattern is clamping: keeping a value within a range. Combine Math.min() and Math.max():
const volume = 140;
const clamped = Math.min(Math.max(volume, 0), 100);
console.log(clamped);
// 100
Random numbers
Math.random() returns a decimal from 0 up to, but not including, 1. To get a random whole number in a range, scale it and round down:
// A random whole number from 1 to 6, like a dice roll
const roll = Math.floor(Math.random() * 6) + 1;
console.log(roll);
// → a number from 1 to 6
Math.random() is fine for games and shuffling a quiz. It is not suitable for anything security-related, such as passwords or tokens. For those, use crypto.getRandomValues() or crypto.randomUUID(), which are available in browsers and Node.js.
Summary
- JavaScript has one floating-point
numbertype for whole numbers and decimals. 0.1 + 0.2is not exactly0.3because of binary floating point. Round for display, use whole pence for money, or compare with a tolerance.- Dividing by zero gives
Infinity; failed maths givesNaN, which spreads. Check withNumber.isNaN()andNumber.isFinite(). - Convert text with
Number()for strict validation, orNumber.parseInt(text, 10)andNumber.parseFloat()to read a leading number. - Round with
Math.round(),floor(),ceil()andtrunc(); format withtoFixed(), which returns a string. - Use
Math.random()for games, never for security.