JavaScript Numbers
JavaScript has one numeric type: a 64-bit IEEE 754 double. No int, no float, no long. That single type stores integers, decimals, hex, binary, scientific notation, and the special values NaN, Infinity, and -Infinity. It’s flexible — but you need to know the precision limits and the BigInt escape hatch for when 64 bits aren’t enough.
Number Literals
42 // integer
3.14 // decimal
-5 // negative
0.5 // 0 prefix optional but recommended
.5 // also valid — ugly
1_000_000 // numeric separator (ES2021) — ignored by engine
1_000_000.50_75 // also works in decimals
6.022e23 // scientific notation: 6.022 × 10²³
1.5e-3 // 0.0015
// Non-decimal bases
0xFF // hex → 255
0b1010 // binary → 10
0o755 // octal → 493
| Literal | Base | Value |
|---|---|---|
42 |
10 | 42 |
0xff, 0xFF |
16 | 255 |
0b1010 |
2 | 10 |
0o755 |
8 | 493 |
2e3 |
scientific | 2000 |
1_000_000n |
BigInt | one million as BigInt |
Tip: Use _ separators in any literal with more than 3 digits. 1_000_000 is instantly readable; 1000000 makes you count zeros.
The 64-bit Float
Internally, every JS number is a binary64. That gives you:
| Property | Value |
|---|---|
Number.MAX_VALUE |
~1.79 × 10³⁰⁸ |
Number.MIN_VALUE |
5 × 10⁻³²⁴ (smallest positive) |
Number.MAX_SAFE_INTEGER |
2⁵³ − 1 = 9_007_199_254_740_991 |
Number.MIN_SAFE_INTEGER |
−(2⁵³ − 1) |
Number.EPSILON |
~2.22 × 10⁻¹⁶ — smallest 1 + ε ≠ 1 |
Number.POSITIVE_INFINITY |
Infinity |
Number.NEGATIVE_INFINITY |
-Infinity |
Number.NaN |
NaN |
2 ** 53 // 9007199254740992 — last safe integer
2 ** 53 + 1 // 9007199254740992 ← lost precision!
Number.MAX_SAFE_INTEGER + 1 // 9007199254740992 same thing
Number.MAX_VALUE * 2 // Infinity
1 / 0 // Infinity
-1 / 0 // -Infinity
0 / 0 // NaN
Integers vs Decimals — Same Type
typeof 42 // "number"
typeof 3.14 // "number"
typeof Infinity // "number"
typeof NaN // "number"
Integer arithmetic is exact within Number.MAX_SAFE_INTEGER:
9_007_199_254_740_991 + 0 // 9007199254740991 ← still safe
9_007_199_254_740_992 + 1 // 9007199254740992 ← already lost
Decimals start losing precision much earlier:
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // false
That’s IEEE 754 — same in Java, C, Python. JavaScript just shows it more often.
NaN — Not a Number
NaN is the result of math that doesn’t produce a number:
0 / 0 // NaN
Math.sqrt(-1) // NaN
"abc" * 2 // NaN
parseInt("xx") // NaN
NaN has one extraordinary property: it’s not equal to itself.
NaN === NaN // false
NaN == NaN // false
NaN !== NaN // true ← the only way to test by equality
// The proper tests
Number.isNaN(NaN) // true
Number.isNaN("hi") // false — strict, doesn't coerce
isNaN("hi") // true — global isNaN coerces "hi" → NaN first; misleading
| Function | Coerces input? | Recommended |
|---|---|---|
Number.isNaN(x) |
❌ no | ✅ Yes |
isNaN(x) (global) |
✅ yes | ❌ no |
Object.is(x, NaN) |
❌ no | ✅ Yes (also works for ±0) |
Infinity
1 / 0 // Infinity
-1 / 0 // -Infinity
Infinity + 1 // Infinity
Infinity - Infinity // NaN
Infinity > 1e308 // true — bigger than any representable number
Number.isFinite(1) // true
Number.isFinite(Infinity) // false
Number.isFinite(NaN) // false
isFinite("42") // true ← coerces, usually not what you want
Safe Integers
A “safe integer” is one that can be represented exactly. Beyond ±2⁵³ you lose precision silently.
Number.isSafeInteger(42) // true
Number.isSafeInteger(2 ** 53) // false ← unsafe
Number.isSafeInteger(2 ** 53 - 1) // true
Number.isSafeInteger(3.14) // false
const userId = 9_007_199_254_740_993;
const next = userId + 1;
console.log(userId === next); // true ← they collapsed to the same value
If your problem domain has integers bigger than 2⁵³ (database row IDs, blockchain values, nanosecond timestamps), use BigInt.
BigInt
BigInt is JavaScript’s arbitrary-precision integer type. Append n to a literal or call BigInt():
const huge = 9_007_199_254_740_993n;
const also = BigInt("9007199254740993");
typeof huge // "bigint"
huge + 1n // 9007199254740994n ← exact!
huge * 100n // 900_719_925_474_099_300n
// BigInt + Number is NOT allowed
huge + 1 // ❌ TypeError
Number(huge) + 1 // 9007199254740994 ← loses precision
huge + BigInt(1) // ✅ 9007199254740994n
Number |
BigInt |
|
|---|---|---|
| Holds decimals | ✅ | ❌ integers only |
| Exact above 2⁵³ | ❌ | ✅ |
typeof |
"number" |
"bigint" |
| Mix with the other | (with explicit conversion) | (with explicit conversion) |
| Use for | General math, decimals | Large IDs, exact integer math |
Warning: JSON.stringify cannot serialise BigInt — it throws. You’ll need to convert to a string first (bigIntValue.toString()) when sending over the wire.
Type Conversion
// String → Number
Number("42") // 42
Number("3.14") // 3.14
Number("42abc") // NaN
Number("") // 0 ← surprising
Number(" 42 ") // 42 ← trims
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number([]) // 0 ← surprising
Number([42]) // 42 ← single element
Number([1, 2]) // NaN
parseInt("42px") // 42 ← reads leading integer
parseInt("0x10") // 16 ← hex prefix
parseInt("10", 2) // 2 ← binary
parseFloat("3.14xx") // 3.14
+"42" // 42 ← unary +, same as Number()
+true // 1
| Want | Use | Notes |
|---|---|---|
| Strict numeric conversion | Number(x) or +x |
"" and null become 0 |
| “Extract leading number” | parseFloat(x) / parseInt(x, 10) |
Always pass base! |
| Round to int | Math.trunc(x) (or Math.floor/ceil) |
|
| Detect parseability | !Number.isNaN(Number(x)) |
Warning: Always pass the radix to parseInt: parseInt("08") in some old engines was 0 (treated as octal). parseInt("08", 10) is safe.
Formatting for Display
(3.14159).toFixed(2) // "3.14" — string
(3.14159).toPrecision(3) // "3.14"
(1234567).toLocaleString("en") // "1,234,567"
(1234.5).toLocaleString("de") // "1.234,5" ← German formatting
(0.4).toLocaleString("en", { style: "percent" }); // "40%"
(123.45).toLocaleString("en", {
style: "currency", currency: "USD"
}); // "$123.45"
Intl.NumberFormat for repeated formatting (much faster):
const fmt = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
fmt.format(1999.99); // "$1,999.99"
Common Patterns
Clamp a value
const clamp = (x, lo, hi) => Math.min(Math.max(x, lo), hi);
clamp(105, 0, 100); // 100
clamp(-5, 0, 100); // 0
Round to N decimals
const round = (n, places) => {
const f = 10 ** places;
return Math.round(n * f) / f;
};
round(1.2345, 2); // 1.23
Random integer in [min, max]
const randInt = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min;
randInt(1, 6); // simulated dice
Parse a “safe” integer from input
function safeInt(input) {
const n = Number(input);
if (!Number.isFinite(n)) return null;
if (!Number.isInteger(n)) return null;
return n;
}
safeInt("42"); // 42
safeInt("3.14"); // null
safeInt("hello"); // null
Real-World Example
// Convert cents → display dollars, robust to bad input
function formatMoney(cents, locale = "en-US", currency = "USD") {
const n = Number(cents);
if (!Number.isFinite(n) || !Number.isInteger(n)) {
return "—"; // invalid input → em-dash
}
return new Intl.NumberFormat(locale, { style: "currency", currency })
.format(n / 100);
}
formatMoney(1999); // "$19.99"
formatMoney(1999, "de-DE", "EUR"); // "19,99 €"
formatMoney("abc"); // "—"
Defensive type-check at the boundary, integer math, locale-aware display formatting.
Best Practices Checklist
- ✅ Use
Number.isNaN,Number.isFinite,Number.isInteger,Number.isSafeInteger - ✅ Use
_separators in numeric literals over 1000 - ✅ Always pass radix to
parseInt(parseInt(x, 10)) - ✅ Use
Intl.NumberFormatfor locale-aware display - ✅ Use integer cents (or BigInt) for money — never floats
- ❌ Don’t compare floats with
===— useMath.abs(a - b) < epsilon - ❌ Don’t use global
isNaN/isFinite - ❌ Don’t trust silent precision loss above
Number.MAX_SAFE_INTEGER
What’s Next?
You know what numbers are. Now let’s run through the methods on the Number type — formatting, parsing, conversion — and the dedicated Math object’s most-used helpers.