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
Numberconstructor itself, such asNumber.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, andNumber.isNaNinstead 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 thanNumber.EPSILON. - Treat
toFixedandtoPrecisionas 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
toLocaleStringfor 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.parseFloatover the bare globalparseInt/parseFloatin modern code — they’re identical in behavior but keep numeric parsing namespaced underNumber, matching ES6+ conventions. - Use
Number.isSafeIntegerwhen 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 usingtoLocaleString(e.g.1234.5should become"$1,234.50"). - Write a function
isWholeNumber(value)that returnstrueonly for actual finite integers, correctly returningfalseforNaN,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.15should become"0f", not just"f"). Hint: check the string’s length after callingtoString(16)and pad it if needed.
Summary
- Numbers are primitives, but JavaScript autoboxes them so you can call methods like
toFixedandtoStringdirectly 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.3and(1.005).toFixed(2)rounding the “wrong” way. - Use
Number.EPSILONfor float comparisons, integer cents (or a library) for money math, andtoLocaleStringfor any number a user will actually read.
