Numbers and BigInt
Use JavaScript's double-precision numbers safely, recognise NaN, Infinity and negative zero, and switch to BigInt for large integers.
What you will learn
- Explain why 0.1 + 0.2 is not 0.3 and how to work around it
- Recognise NaN, Infinity and -0, and test for them correctly
- Know the safe integer range and use BigInt beyond it
- Use the arithmetic operators and common Math functions
On this page
JavaScript has one number type for everything from 1 to 3.14159. That keeps programs simple and brings a handful of surprises, all of which come from how the computer stores numbers. The fundamentals lesson on expressions and operators explains floating-point imprecision in general. This lesson shows what it looks like in JavaScript and what to do about it.
One type: the double
Every number is a 64-bit IEEE 754 double. It stores a sign, an exponent and a fraction in binary, which is why 42 and 3.14 are the same type. Whole numbers are exact up to a limit that is explained below. Fractions are usually approximations, because many decimal fractions have no exact binary form.
console.log(0.1 + 0.2);console.log(0.1 + 0.2 === 0.3);console.log(1.1 + 2.2);console.log(1 / 3);
const close = Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;console.log(close);
console.log((0.1 + 0.2).toFixed(2));console.log(Number((0.1 + 0.2).toFixed(2)) === 0.3);0.30000000000000004false3.30000000000000030.3333333333333333true0.30true0.1 + 0.2 is 0.30000000000000004, and this is not a JavaScript bug: every language that uses doubles gives the same answer. Two lessons follow:
- Never compare computed fractions with
===. Compare the difference with a small tolerance, such asNumber.EPSILON, which is the gap between 1 and the next larger double. That tolerance suits values near 1, so scale it for larger numbers. - For display, round with
toFixed(2). It returns text, rounded to the number of decimals you ask for. Convert it back withNumber(...)if you need a number again.
Money: use whole cents
Because decimals are approximate, do not compute money with fractional values. Keep amounts in the smallest unit, as whole numbers, and divide only to display.
console.log(1.15 * 100);console.log(0.07 * 100);
const priceCents = 1999;const quantity = 3;const totalCents = priceCents * quantity;
console.log(totalCents);console.log((totalCents / 100).toFixed(2));console.log(Math.round(1.15 * 100));114.999999999999997.000000000000001599759.971151.15 * 100 is not 115, but 1999 * 3 in cents is exactly 5997. Whole numbers stay exact, so the only division happens at the end, for display. If a product like 1.15 * 100 does happen, Math.round brings it back to the nearest whole number.
NaN, Infinity and negative zero
Three special values come with the number type.
console.log(1 / 0);console.log(-1 / 0);console.log(0 / 0);console.log(Number("abc"));console.log(NaN === NaN);console.log(Number.isNaN(NaN), Number.isNaN("abc"));console.log(isNaN("abc"));console.log(Number.isFinite(1 / 0), Number.isFinite(42));
const negativeZero = 0 * -1;console.log(negativeZero, negativeZero === 0, Object.is(negativeZero, 0));console.log(String(negativeZero));Infinity-InfinityNaNNaNfalsetrue falsetruefalse true-0 true false0Infinityand-Infinityappear when a result is too large, or when you divide by zero. Division by zero is not an error.NaN(“not a number”) is the result of a failed numeric operation, such as0 / 0orNumber("abc"). It is the only value that is not equal to itself, soNaN === NaNisfalse. Test for it withNumber.isNaN(value). The older globalisNaN("abc")converts its argument to a number first and answerstruefor text, which is rarely what you mean. PreferNumber.isNaN.-0is a separate value that compares equal to0with===. It can come out of operations such as0 * -1.console.logshows it as-0, andStringhides the sign. You will rarely care, butObject.iscan tell them apart.
Number.isFinite(value) is false for Infinity, -Infinity and NaN, and also for anything that is not a number type.
Safe integers
A double stores whole numbers exactly only up to 2 to the power of 53, minus 1. That is Number.MAX_SAFE_INTEGER, which is 9007199254740991. Above it, neighbouring integers collide.
const big = Number.MAX_SAFE_INTEGER;
console.log(big);console.log(big + 1);console.log(big + 2);console.log(big + 1 === big + 2);console.log(Number.isSafeInteger(big), Number.isSafeInteger(big + 1));console.log(Number.isInteger(5.0), Number.isInteger(5.5));900719925474099190071992547409929007199254740992truetrue falsetrue falseNumber.isSafeInteger(n) checks that n is a whole number in the safe range. Number.isInteger(n) only checks that it is whole, and 5.0 is whole because it is the same value as 5.
Operators and Math
The arithmetic operators are the ones you expect, plus % for the remainder and ** for powers.
console.log(7 / 2);console.log(Math.trunc(7 / 2));console.log(7 % 3, -7 % 3);console.log(2 ** 10, 2 ** -1);console.log(Math.round(2.5), Math.round(-2.5), Math.round(2.4));console.log(Math.floor(-3.5), Math.ceil(-3.5), Math.trunc(-3.5));console.log(Math.max(3, 9, 4), Math.min(3, 9, 4), Math.abs(-8), Math.sqrt(81));console.log(1_000_000, 0xff, 0b1010, 1e3);console.log((255).toString(16), (5).toString(2));3.531 -11024 0.53 -2 2-4 -3 -39 3 8 91000000 255 10 1000ff 101Look at these details:
/always gives a fractional result when needed.7 / 2is3.5. There is no integer division, so useMath.trunc(7 / 2)to drop the fraction.%takes its sign from the left operand:-7 % 3is-1.Math.roundrounds halves towards positive infinity, soMath.round(2.5)is3andMath.round(-2.5)is-2.Math.floorrounds down,Math.ceilup, andMath.trunctowards zero.- Numeric literals can use
0xfor hexadecimal,0bfor binary andefor powers of ten. Underscores in a literal, such as1_000_000, are separators that make long numbers readable and do not change the value. They have been in the language since ES2021. toString(base)writes a number in another base.
Unary minus, powers and bitwise operators
A few operator rules surprise people. ** groups from the right, so 2 ** 3 ** 2 is 2 ** 9. A minus sign directly in front of a base is a SyntaxError: -2 ** 2 is rejected, because it could mean either (-2) ** 2 or -(2 ** 2). Write the brackets you mean.
JavaScript also has operators that work on the individual bits of a number: & (and), | (or), ^ (exclusive or), ~ (not), <<, >> and >>> (shifts). You will rarely need them. They first convert their operands to 32-bit integers, which is why x | 0 and ~~x were once used to drop a fraction. Prefer Math.trunc for that.
console.log(5 & 3, 5 | 3, 5 ^ 3);console.log(1 << 3, 16 >> 2);console.log(7.9 | 0, ~~-7.9, Math.trunc(-7.9));console.log(2 ** 3 ** 2, (2 ** 3) ** 2);1 7 68 47 -7 -7512 64The first line shows the bits of 5 (101) and 3 (011) combined three ways. The second shifts bits left and right, which doubles or halves a number for each step. The third shows the truncating tricks agreeing with Math.trunc. The last line shows that ** is right-to-left, so brackets change the answer.
Math has many more functions, such as Math.max, Math.min, Math.abs, Math.sqrt and Math.random. Math is not a type but a ready-made object of functions, so you never write new Math.
BigInt
When you need whole numbers beyond the safe range, use a BigInt. Write a literal with a trailing n, or convert with BigInt(...). BigInts have been in the language since ES2020, and they can grow as large as memory allows.
const big = 2n ** 64n;console.log(big);console.log(typeof big);console.log(5n / 2n, 7n % 3n);console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n);console.log(10n > 5, 10n == 10, 10n === 10);console.log(1n + BigInt(1), Number(1n) + 1);18446744073709551616nbigint2n 1n9007199254740993ntrue true false2n 2You cannot mix a BigInt and a number in arithmetic. This line stops the program:
console.log(1n + 1);TypeError: Cannot mix BigInt and other types, use explicit conversionsThe last line of bigint_basics.mjs shows the two fixes: convert the number with BigInt(1), or convert the BigInt with Number(1n).
The rules to remember:
typeofa BigInt is"bigint". It is a separate primitive type.- Division truncates:
5n / 2nis2n. There are no fractional BigInts. - Mixing a BigInt and a number in arithmetic throws a
TypeError, so convert one side explicitly. Converting a huge BigInt to a number loses precision. - Comparisons work across the two types:
10n > 5istrueand10n == 10istrue, but10n === 10isfalsebecause the types differ. Mathfunctions do not accept BigInts.
Here is a product that would overflow the safe range with ordinary numbers:
const product = 1_000_000_007n * 998_244_353n * 12_345_678_910_111n;
console.log(product);console.log(product.toString().length);12324004342237530144969502472281n32The result has far more digits than Number.MAX_SAFE_INTEGER, and every digit is exact. toString() turns the BigInt into digits as text, and .length counts them.
Common pitfalls
- Comparing fractions with
===. Use a tolerance, or work in whole units. - Using
isNaN. It converts first. UseNumber.isNaN. - Doing money in dollars. Use cents or another smallest unit.
- Expecting integer division.
7 / 2is3.5. - Mixing BigInt and number. It throws. Convert explicitly.
- Using
toFixedand treating the result as a number. It returns a string. - Writing
-2 ** 2. It is aSyntaxError. Add brackets.
Four friends split a bill of 8750 cents. Using whole-number arithmetic only, print what each pays as a string with two decimals, in the form Each pays: 21.87, and the cents left over in the form Left over: 2 cents. Each share is rounded down.
Input: none. Expected output: Each pays: 21.87 then Left over: 2 cents.
Show solution
const billCents = 8750;const people = 4;
const shareCents = Math.floor(billCents / people);const leftOver = billCents % people;
console.log(`Each pays: ${(shareCents / 100).toFixed(2)}`);console.log(`Left over: ${leftOver} cents`);Each pays: 21.87Left over: 2 centsMath.floor drops the fraction of the division, and % gives the remainder. The only division by 100 happens at the end, for display.
Multiply 99_999_999_999n by 99_999_999_999n as BigInts, print the result, and then print the same multiplication done with ordinary numbers. Which of the two is exact?
Input: none. Expected output: 9999999999800000000001n and 9.9999999998e+21.
Show solution
console.log(99_999_999_999n * 99_999_999_999n);console.log(99_999_999_999 * 99_999_999_999);9999999999800000000001n9.9999999998e+21The BigInt result is exact. The ordinary number is rounded to about 16 significant digits and printed in exponent form.
Print three answers, one per line: whether 2 ** 53 is a safe integer, whether 2 ** 53 - 1 is, and whether Number.MAX_SAFE_INTEGER + 2 equals Number.MAX_SAFE_INTEGER + 1.
Input: none. Expected output: false, true, true.
Show solution
console.log(Number.isSafeInteger(2 ** 53));console.log(Number.isSafeInteger(2 ** 53 - 1));console.log(Number.MAX_SAFE_INTEGER + 2 === Number.MAX_SAFE_INTEGER + 1);falsetruetrueThe limit is 2 ** 53 - 1. One step above it, two neighbouring integers collapse into the same double, so the last comparison is true.
Summary
- All ordinary numbers are doubles, so
0.1 + 0.2is not0.3. Compare with a tolerance and keep money in whole cents. NaNis never equal to itself: test withNumber.isNaN. Dividing by zero givesInfinity.- Integers are exact up to
Number.MAX_SAFE_INTEGER. Past that, use BigInt (123n), and do not mix it with numbers.