Skip to content
JavaScript js numbers 4 min read

Number Methods & Formatting

Once you have a Number, the next job is usually presenting it cleanly or checking that it is what you expect. JavaScript splits this work in two: instance methods on number values handle formatting (turning a number into a string with a chosen number of digits or radix), while the static methods on the Number constructor handle inspection (asking whether a value is an integer, finite, or a real number at all). This page covers both groups and the parsing pitfalls that send unwary code into NaN territory.

Formatting with toFixed and toPrecision

toFixed(digits) returns a string with exactly the given number of digits after the decimal point, rounding or zero-padding as needed. It is the go-to for money and other fixed-decimal displays. toPrecision(digits) instead controls the total number of significant digits, switching to exponential notation when the magnitude demands it.

const price = 19.5;
console.log(price.toFixed(2));     // pad to 2 decimals
console.log((3.14159).toFixed(2)); // round to 2 decimals
console.log((1234.5678).toPrecision(5)); // 5 significant digits
console.log((0.00004567).toPrecision(2)); // switches to exponential

Output:

19.50
3.14
1234.6
0.000046

Both methods return strings, not numbers — wrap them in Number(...) or a unary + if you need to compute further. Because the underlying value is still a binary float, rounding can occasionally surprise you: (1.005).toFixed(2) yields "1.00", not "1.01", because 1.005 is stored as a hair under that value.

For locale-aware currency, thousands separators, and percentages, prefer Intl.NumberFormat over toFixed. It handles grouping, currency symbols, and regional decimal marks that toFixed cannot.

const fmt = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
});
console.log(fmt.format(1234567.891));

Output:

$1,234,567.89

Converting between bases with toString

Calling toString(radix) on a number produces its textual representation in any base from 2 to 36. With no argument it defaults to base 10. This is handy for binary, hexadecimal, and even compact base-36 identifiers.

const n = 255;
console.log(n.toString(2));   // binary
console.log(n.toString(16));  // hexadecimal
console.log(n.toString(8));   // octal
console.log((35).toString(36)); // digits 0-9 then a-z

Output:

11111111
ff
377
z

To go the other way — a string in some base back to a number — use parseInt(str, radix). The pair n.toString(16) and parseInt(hex, 16) round-trips a value through hexadecimal.

Inspecting numbers: the static methods

The Number.* predicates answer questions about a value safely, without the type coercion that plagues their global namesakes. This is the single most important reason to prefer them.

MethodReturns true when…Coerces argument?
Number.isNaN(x)x is exactly the value NaNNo
Number.isFinite(x)x is a number and not ±Infinity/NaNNo
Number.isInteger(x)x is a number with no fractional partNo
Number.isSafeInteger(x)x is an integer within ±(2^53 − 1)No
isNaN(x) (global)x becomes NaN after coercionYes
isFinite(x) (global)x coerces to a finite numberYes
console.log(Number.isNaN(NaN));        // true
console.log(Number.isNaN("hello"));    // false — a string is not NaN
console.log(isNaN("hello"));           // true — global coerces first!

console.log(Number.isInteger(4.0));    // true
console.log(Number.isInteger(4.5));    // false
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite("42"));    // false — no coercion

Output:

true
false
true
true
false
false
false

The contrast on line 3 is the crux: the global isNaN("hello") first converts the string to a number (which fails, yielding NaN) and then reports true, which is almost never what you meant. Number.isNaN checks the value as-is. As a rule, always reach for the Number.-prefixed versions.

Detecting NaN reliably

NaN is the only JavaScript value not equal to itself, which makes === useless for detecting it and Number.isNaN (or Object.is) essential.

const result = Number("not a number"); // NaN
console.log(result === NaN);           // false — NaN never equals NaN
console.log(Number.isNaN(result));     // true
console.log(Object.is(result, NaN));   // true

Output:

false
true
true

Parsing pitfalls

Turning user input into numbers is where bugs cluster. Number(str) is strict: the whole string must be a valid number or you get NaN. parseInt/parseFloat are lenient: they read as far as they can and ignore trailing garbage.

console.log(Number("42px"));    // NaN — strict
console.log(parseInt("42px"));  // 42 — stops at 'p'
console.log(parseFloat("3.14m")); // 3.14
console.log(Number(""));        // 0 — empty string is zero!
console.log(parseInt("0x1f"));  // 31 — auto-detects hex
console.log(Number("  7  "));   // 7 — whitespace trimmed

Output:

NaN
42
3.14
0
31
7

Always pass an explicit radix to parseInt(str, 10). Without it, strings like "0x1f" are read as hexadecimal, and the empty-string-to-0 quirk of Number can mask missing input. Validate with Number.isNaN after parsing.

Best Practices

  • Use toFixed for fixed-decimal display but remember it returns a string and rounds a stored binary float, not the literal you typed.
  • Reach for Intl.NumberFormat for currency, grouping, and locale-aware output instead of hand-rolling with toFixed.
  • Always prefer Number.isNaN, Number.isFinite, and Number.isInteger over the coercing global functions.
  • Detect NaN with Number.isNaN or Object.is — never with === NaN, which is always false.
  • Pass an explicit radix to parseInt(str, 10) to avoid surprise hex parsing.
  • Choose Number(str) when input must be fully valid, and parseInt/parseFloat only when trailing units or text are expected.
Last updated June 1, 2026
Was this helpful?