One Number Type for Everything (Almost)
Most languages give you separate types for integers and floats. JavaScript, historically, gave you one: Number. Whether you wrote 42, 3.14, or -0.001, you got the same primitive — a 64-bit IEEE 754 double-precision float.
That's convenient — no casting between int and float, no overflow at 2^31. But the float representation has consequences, and they bite beginners regularly. A second numeric type, BigInt, was added in 2020 to handle the cases where Number falls short.
The Floating-Point Surprise
Run this:
The first line prints 0.30000000000000004. The second prints false. This isn't a JavaScript quirk — Python, Java, C, and any language using IEEE 754 floats behaves the same way.
The reason: 0.1 and 0.2 can't be represented exactly in binary, just like 1/3 can't be written exactly in decimal. The closest binary approximation is stored, and tiny errors accumulate. The mental model is: treat Number values involving decimals as approximations that happen to be very close to what you wrote.
For money, don't store $19.99 as 19.99. Store cents as integers — 1999 — and format on display. That's the single best habit for dodging float bugs.
Comparing Floats Safely
Since equality is unreliable, compare with a tolerance when you need to:
Number.EPSILON is the smallest difference between 1 and the next representable number — a reasonable default tolerance for values near 1. For very large or very small magnitudes, you'll want a tolerance that scales with the inputs.
The Safe Integer Range
Integers up to a certain size are exact in a 64-bit float. Past that, you start losing precision one bit at a time:
2^53 - 1 is the last integer where every integer below it is representable. Beyond that, some integers simply don't exist in the Number type — they round to their nearest neighbour. That's a silent data-corruption bug waiting to happen if you're parsing 64-bit IDs from a database as plain JSON numbers.
Enter BigInt
BigInt is a separate primitive for arbitrary-precision integers. You create one by appending n to an integer literal, or by calling BigInt(...):
BigInts have no upper limit other than available memory. They're the right tool for:
- Database IDs or Twitter/X snowflake IDs that exceed
2^53. - Cryptographic math.
- Any integer arithmetic where exact results matter more than raw speed.
They're not the right tool for everyday counters, array indices, or money-as-cents — regular Number is faster and interoperates with every API in the language.
BigInt Arithmetic
All the usual operators work, as long as both operands are BigInts:
Division truncates toward zero — there are no fractional BigInts. If you need a fraction, you're back in Number territory (or using a decimal library).
Don't Mix Types
The rule that trips people up: you can't mix Number and BigInt in the same expression.
Comparison is the one exception — <, >, == coerce across the boundary:
So == sees them as equal, === doesn't. If you've already adopted === everywhere (you should), treat numeric comparisons between the two types as a design smell — pick one side and convert.
Converting Between Them
Two conversions, two gotchas:
Going Number → BigInt is strict: decimals and NaN throw. Going BigInt → Number is permissive but lossy — anything above MAX_SAFE_INTEGER rounds. If you're converting a BigInt you received from a server, ask yourself whether you actually need to.
Special Number Values
While we're here, three values in the Number type that aren't numbers in the mathematical sense:
Infinity and -Infinity show up when you divide by zero or overflow the float range. NaN ("not a number") shows up when an arithmetic operation doesn't produce a meaningful result.
NaN is famously not equal to itself — that's part of the IEEE 754 spec, not a JS bug. Use Number.isNaN(x) to check. The older global isNaN coerces its argument first, which gives wrong answers (isNaN("hello") returns true). Always prefer the Number.isNaN version.
Parsing Numbers From Strings
User input and JSON numbers often arrive as strings. Three ways to convert:
Number() is strict — anything non-numeric gives NaN, except the empty string and whitespace, which give 0. parseInt and parseFloat are forgiving — they read as far as they can and stop. Pick whichever matches your intent, and check for NaN before using the result.
For parsing BigInts from strings, use BigInt("123") — it's strict and throws on bad input.
A Quick Rulebook
- For counters, math, coordinates, and most everyday numbers: use
Number. - For money: scale to integer cents and use
Number, or reach for a decimal library. - For integers larger than
2^53(database IDs, crypto, combinatorics): useBigIntwith thensuffix. - Compare floats with a tolerance, not
===. - Check for invalid results with
Number.isNaNandNumber.isFinite, not the global versions. - Don't mix
NumberandBigIntin the same expression — convert explicitly.
Next: null vs undefined
JavaScript has two ways of saying "no value" — null and undefined — and they're not interchangeable. Up next: what each one means, how they differ, and which to use when.
Frequently Asked Questions
Why does 0.1 + 0.2 not equal 0.3 in JavaScript?
Because JavaScript's Number type is a 64-bit IEEE 754 float, and 0.1 and 0.2 can't be represented exactly in binary. The result is 0.30000000000000004. This isn't a JavaScript bug — it happens in Python, Java, and any other language using the same float format. For money, scale to integers (cents) or use a decimal library.
What is BigInt in JavaScript and when should I use it?
BigInt is a separate numeric primitive for integers beyond Number.MAX_SAFE_INTEGER (2^53 - 1). Create one with a trailing n — 9007199254740993n — or BigInt(value). Use it for 64-bit database IDs, cryptography, or any integer math where precision matters more than speed.
Can I mix Number and BigInt in JavaScript?
No. 1n + 1 throws TypeError: Cannot mix BigInt and other types. Convert explicitly with BigInt(n) or Number(b). Comparison operators like < and == do work across the two, but === returns false because the types differ.