Numbers and Math

Understand how JavaScript numbers behave, why decimals sometimes surprise you, and how to parse, round and calculate safely.

  • Beginner
  • 7 min read
  • Updated 17 Oct 2026

What you will learn

  • Parse and round numbers
  • Explain why 0.1 + 0.2 is not 0.3
  • Use the Math object for rounding, limits and random numbers
  • Format numbers for display with toFixed

Before you start

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.

JavaScript
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:

JavaScript
const population = 67_000_000;
console.log(population);
// 67000000

Why 0.1 + 0.2 is not 0.3

Try this in the console:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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():

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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.

JavaScript
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 from floor() 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:

JavaScript
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

JavaScript
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():

JavaScript
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:

JavaScript
// 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 number type for whole numbers and decimals.
  • 0.1 + 0.2 is not exactly 0.3 because of binary floating point. Round for display, use whole pence for money, or compare with a tolerance.
  • Dividing by zero gives Infinity; failed maths gives NaN, which spreads. Check with Number.isNaN() and Number.isFinite().
  • Convert text with Number() for strict validation, or Number.parseInt(text, 10) and Number.parseFloat() to read a leading number.
  • Round with Math.round(), floor(), ceil() and trunc(); format with toFixed(), which returns a string.
  • Use Math.random() for games, never for security.

Further reading