JavaScript Number Methods

Every number you write in JavaScript — 42, 3.14, -7 — is backed by the built-in Number type, and that type ships with a full toolbox of methods for formatting, parsing, validating, and comparing numeric values. Whether you need to round a price to two decimal places, print a number in hexadecimal, format a value for a specific locale, or safely check whether something is really a usable number, JavaScript’s Number methods give you a standardized way to do it instead of hand-rolling string manipulation or regular expressions. This lesson covers every commonly used Number method, how numbers work internally, and the floating-point mistakes that trip up even experienced developers.

Overview: How Number Methods Work

In JavaScript, a number literal like 42 is a primitive value, not an object — it has no properties or methods sitting in memory next to it. So when you write (42).toString(), the engine performs a step called autoboxing: it temporarily wraps the primitive in a hidden Number wrapper object, looks up toString on Number.prototype, calls it, and then immediately discards the wrapper. This happens instantly and invisibly — you never see the wrapper object itself. It’s the same mechanism that lets you call .toUpperCase() on a string primitive.

This is also why 42.toString() throws a syntax error: the parser sees 42. and assumes you’re starting a decimal literal like 42.0, then gets confused by the following toString. Wrapping the number in parentheses, using a variable, or adding a space (42 .toString()) all avoid the ambiguity — in practice, parentheses or a variable are the clean, idiomatic choices.

Number methods come in two flavors, exactly like Math:

  • Instance methods — called on an actual number value, such as someNumber.toFixed(2). These format or convert that specific value.
  • Static methods — called directly on the Number constructor itself, such as Number.isInteger(x). These typically validate or parse an arbitrary input, which might not even be a number yet.

One more crucial fact underlies almost everything in this lesson: JavaScript stores all numbers as 64-bit IEEE 754 floating-point values, whether they look like integers or decimals. Binary floating point cannot represent most decimal fractions exactly — the same way 1/3 can’t be written exactly in base 10. This is why 0.1 + 0.2 doesn’t equal 0.3, and it’s precisely why methods like toFixed, toPrecision, and constants like Number.EPSILON exist — they give you controlled, predictable ways to work around a limitation baked into how computers represent numbers.

Syntax

numberValue.methodName(arguments)
Number.staticMethodName(value)
Method Type Purpose
toFixed(digits) Instance Returns a string with a fixed number of digits after the decimal point.
toPrecision(digits) Instance Returns a string with a fixed number of total significant digits.
toString(radix) Instance Converts the number to a string, optionally in a given base (2–36).
toLocaleString(locale, options) Instance Formats a number using locale-aware separators, currency, or units.
valueOf() Instance Returns the primitive number value wrapped by a Number object.
Number.isInteger(value) Static True if the value is a number with no fractional part (no coercion).
Number.isFinite(value) Static True if the value is a finite number (no coercion, unlike global isFinite).
Number.isNaN(value) Static True only if the value is exactly NaN (no coercion, unlike global isNaN).
Number.isSafeInteger(value) Static True if the value can be represented exactly as an integer without precision loss.
Number.parseFloat(str) Static Parses a leading floating-point number out of a string.
Number.parseInt(str, radix) Static Parses a leading integer out of a string in the given base.
Number.EPSILON Static constant The smallest difference between 1 and the next representable number — used for float comparisons.
Number.MAX_SAFE_INTEGER Static constant The largest integer (2^53 − 1) that JavaScript can represent exactly.

Examples

Example 1: Formatting numbers for display

const price = 49.5;
const quantity = 3;
const total = price * quantity;

console.log(total);
console.log(total.toFixed(2));
console.log(total.toPrecision(5));
console.log((255).toString(16));
console.log((8).toString(2));

Output:

148.5
148.50
148.50
ff
1000

toFixed(2) always returns a string with exactly two digits after the decimal point, padding with zeros if needed — that’s why 148.5 becomes the string "148.50". toPrecision(5) instead fixes the total count of significant digits, which in this case produces the same visual result. toString(16) and toString(2) convert the integer to hexadecimal and binary respectively — useful for color codes, bitmasks, and low-level debugging. Note that toFixed and toString both return strings, not numbers, so chaining further math on the result requires converting back with Number() or a unary +.

Example 2: Validating numeric input

function processValue(value) {
  if (!Number.isFinite(value)) {
    return 'Not a finite number';
  }
  if (Number.isInteger(value)) {
    return `${value} is an integer`;
  }
  return `${value} is a decimal`;
}

console.log(processValue(42));
console.log(processValue(3.14));
console.log(processValue(Infinity));
console.log(processValue(NaN));
console.log(Number.isNaN(NaN));
console.log(Number.isNaN('hello'));
console.log(isNaN('hello'));
console.log(Number.parseFloat('3.14abc'));
console.log(Number.parseInt('42px'));

Output:

42 is an integer
3.14 is a decimal
Not a finite number
Not a finite number
true
false
true
3.14
42

Number.isFinite rejects both Infinity and NaN, which is why processValue reports both as not finite. Notice the difference between Number.isNaN('hello') (false) and the global isNaN('hello') (true): the global function coerces its argument to a number first — turning the string into NaN before testing it — while Number.isNaN only returns true for a value that is already, strictly, the number NaN. Number.parseFloat and Number.parseInt read as much of a leading numeric pattern as they can and stop at the first character that doesn’t fit, which is why '3.14abc' and '42px' still parse successfully.

Example 3: Safe integers, floating-point comparison, and locale formatting

function isSafeSum(a, b) {
  const sum = a + b;
  return Number.isSafeInteger(sum);
}

console.log(Number.MAX_SAFE_INTEGER);
console.log(isSafeSum(Number.MAX_SAFE_INTEGER, 1));
console.log(isSafeSum(10, 20));

function nearlyEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(nearlyEqual(0.1 + 0.2, 0.3));

const population = 8123456;
console.log(population.toLocaleString('en-US'));
console.log(population.toLocaleString('en-US', { style: 'currency', currency: 'USD' }));

Output:

9007199254740991
false
true
0.30000000000000004
false
true
8,123,456
$8,123,456.00

Number.MAX_SAFE_INTEGER (2^53 − 1) is the largest integer JavaScript can represent without losing precision; adding 1 to it produces a value that is no longer a safe integer, which Number.isSafeInteger correctly flags as false. The classic 0.1 + 0.2 example shows binary floating-point imprecision directly — the real fix isn’t to compare with ===, but to check whether two values are close enough using Number.EPSILON as a tolerance, exactly as nearlyEqual does. Finally, toLocaleString is the right tool whenever you’re displaying numbers to a human: it adds thousands separators and can even format full currency strings for a given locale, without you writing any manual string-splitting logic.

Under the Hood: How the Engine Formats a Number

Every JavaScript number lives internally as a 64-bit IEEE 754 double: 1 sign bit, 11 exponent bits, and 52 mantissa (fraction) bits. When you call total.toFixed(2), the engine doesn’t just “cut off” digits — it takes the exact binary value stored in those 64 bits, determines the closest decimal value at the requested precision, and produces a string. Because most decimal fractions (like 0.1, 0.2, or 1.005) have no exact binary representation, the 64-bit value stored is already a tiny bit off from the “intended” decimal number, and rounding methods sometimes round based on that stored value rather than the number as you typed it.

For toString(radix), the engine repeatedly divides the number by the target base and collects the remainders to build up the digit string — the same long-division algorithm you’d do by hand, just done directly on the binary representation. For Number.parseInt and Number.parseFloat, the engine scans the string character by character from the start, building up a numeric value as long as characters keep matching a valid number pattern, and simply stops — without throwing an error — at the first character that doesn’t fit.

toLocaleString is different again: it hands the number off to the ECMAScript Internationalization API (Intl.NumberFormat under the hood), which consults locale-specific rules for digit grouping, decimal separators, and currency symbols before producing the final string.

Common Mistakes

Mistake 1: Trusting toFixed for exact money math

const price = 1.005;
console.log(price.toFixed(2));

You might expect "1.01", but this actually prints "1.00". The literal 1.005 cannot be stored exactly in binary floating point — the real stored value is very slightly less than 1.005 — so when toFixed rounds, it rounds down. This is one of the most famous JavaScript gotchas, and it means toFixed is unreliable for anything involving exact monetary rounding.

The reliable fix is to avoid doing exact money arithmetic in floating-point decimals at all — work in integer cents (or the smallest currency unit) instead, and only convert to a decimal string at the very end for display:

function roundToCents(amountInCents) {
  return amountInCents / 100;
}

const totalCents = 1005; // store money as integer cents
console.log(roundToCents(totalCents).toFixed(2));

Output: 10.05

Because totalCents is a whole number the whole time, there’s no ambiguous rounding step — the division only happens once, right before display.

Mistake 2: Using the global isNaN instead of Number.isNaN

console.log(isNaN('hello'));
console.log(Number.isNaN('hello'));
console.log(isNaN(undefined));
console.log(Number.isNaN(undefined));

Output:

true
false
true
false

The global isNaN() function coerces its argument to a number before testing it, so any non-numeric string or value that can’t convert cleanly (like 'hello' or undefined) reports true — even though the original value was never actually the number NaN. This produces false positives in validation code. Number.isNaN() performs no coercion: it only returns true when the argument’s type is number and its value is NaN. For validating whether a value is genuinely NaN, always prefer Number.isNaN.

Best Practices

  • Use Number.isInteger, Number.isFinite, and Number.isNaN instead of their global counterparts — they skip type coercion and give you predictable, strict results.
  • Never compare floating-point results with ===; instead check that the difference is smaller than Number.EPSILON.
  • Treat toFixed and toPrecision as display-formatting tools that return strings, not as precise rounding tools for further math.
  • For money, store values as integer cents (or use a dedicated decimal/money library) rather than doing arithmetic directly on floating-point dollars.
  • Use toLocaleString for any number shown to a user — it handles thousands separators, decimals, and currency symbols correctly for the target locale.
  • Always pass an explicit radix to parseInt (e.g. Number.parseInt(str, 10)) to avoid ambiguity with strings that look like they start with a leading zero or other base indicator.
  • Prefer Number.parseInt / Number.parseFloat over the bare global parseInt / parseFloat in modern code — they’re identical in behavior but keep numeric parsing namespaced under Number, matching ES6+ conventions.
  • Use Number.isSafeInteger when working with IDs, counters, or any integer that might exceed 2^53 — beyond that range, JavaScript numbers silently lose precision.

Practice Exercises

  • Write a function formatCurrency(amount) that takes a number and returns it formatted as US currency using toLocaleString (e.g. 1234.5 should become "$1,234.50").
  • Write a function isWholeNumber(value) that returns true only for actual finite integers, correctly returning false for NaN, Infinity, decimals, and non-numbers — without using coercing global functions.
  • Write a function toHexColor(n) that takes an integer from 0–255 and returns a two-character, lowercase hexadecimal string (e.g. 15 should become "0f", not just "f"). Hint: check the string’s length after calling toString(16) and pad it if needed.

Summary

  • Numbers are primitives, but JavaScript autoboxes them so you can call methods like toFixed and toString directly on a number value.
  • Instance methods (toFixed, toPrecision, toString, toLocaleString) format an existing number into a string.
  • Static methods (Number.isInteger, Number.isFinite, Number.isNaN, Number.isSafeInteger) validate arbitrary values strictly, without coercion.
  • Numbers are stored as IEEE 754 doubles, so most decimal fractions are only approximated in binary — this is the root cause of surprises like 0.1 + 0.2 !== 0.3 and (1.005).toFixed(2) rounding the “wrong” way.
  • Use Number.EPSILON for float comparisons, integer cents (or a library) for money math, and toLocaleString for any number a user will actually read.