JavaScript Numbers

6 minute read

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.NumberFormat for locale-aware display
  • ✅ Use integer cents (or BigInt) for money — never floats
  • ❌ Don’t compare floats with === — use Math.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.

← Previous: JS String Methods Next: JS Number Methods →

Updated: