When you need a string with a specific number of digits after the decimal point—no scientific notation, just plain fixed-point text—reach for Number.prototype.toFixed
. It formats a number into a string with exactly the number of fractional digits you ask for, rounding if necessary and padding with zeros when there aren’t enough digits.
(12.345).toFixed(2); // "12.35"
(12).toFixed(3); // "12.000"
Syntax and return value
Call toFixed
on a number and pass the number of digits you want after the decimal. The result is always a string. If you omit the argument, it defaults to 0
.
const n = 1234.5;
n.toFixed(); // "1235"
n.toFixed(1); // "1234.5"
typeof n.toFixed(1); // "string"
The allowed range for digits
is 0 to 100 (inclusive). Passing a value outside that range throws RangeError
.
Rounding rules, precisely
toFixed
rounds to the nearest value at the requested precision. If the value is exactly halfway, it rounds up (away from zero, after the sign is separated), per the ECMAScript algorithm that selects the larger candidate in a tie. This is why 2.35
with one fractional digit becomes "2.4"
.
(2.34).toFixed(1); // "2.3"
(2.35).toFixed(1); // "2.4" // tie rounds up
(-2.35).toFixed(1); // "-2.4" // still away from zero overall
You may see surprising results when binary floating-point cannot exactly represent your input. A classic example is 2.55
, whose closest IEEE-754 representation is slightly less than 2.55, so it rounds down to "2.5"
at one decimal place. This is not a bug in toFixed
; it’s the nature of floating-point.
(2.55).toFixed(1); // "2.5" // representation is a hair under 2.55
(2.449999999999999999).toFixed(1); // "2.5" // same value as 2.45, so it rounds up
Range, errors, and edge cases
If digits
is not an integer in the valid range, you’ll get a RangeError
. If you invoke toFixed
with a non-number this
value, you’ll get a TypeError
. Keep in mind that the method is not generic; it’s meant for Number
values (or Number wrappers).
(123).toFixed(101); // RangeError
Number.prototype.toFixed.call("123", 2); // TypeError
Large numbers, non-finite values, and when exponential notation appears
For extremely large magnitudes, toFixed
defers to the same algorithm as Number.prototype.toString
and can use exponential notation when the absolute value is at least 10^21. Non-finite values round-trip to string forms like "Infinity"
or "NaN"
. Also, toFixed(0)
can preserve more detail than toString()
for some integers near the edges of precision.
(1.23e20).toFixed(2); // "123000000000000000000.00"
(6.02e23).toFixed(50); // "6.019999999999999e+23" // still exponential
(1000000000000000128).toString(); // "1000000000000000100"
(1000000000000000128).toFixed(0); // "1000000000000000128"
Negative numbers, -0
, and string signs
toFixed
handles the sign separately from magnitude. It pulls off the sign, rounds the absolute value, then concatenates the sign back. That detail matters because very small negative numbers can round to "0.00"
with a minus sign, producing "-0.00"
—a consequence of IEEE-754 signed zero and the spec’s sign-preserving steps. If you prefer to normalize it to "0.00"
, coerce the string after formatting.
(-0.0004).toFixed(2); // "-0.00"
(-0.0004).toFixed(2).replace(/^-0\./, "0."); // "0.00"
One more practical gotcha: member access binds tighter than unary -
. If you write -2.34.toFixed(1)
, the call happens on 2.34
, and then the result is negated back to a number. Wrap negatives in parentheses when you want a string result.
-2.34.toFixed(1); // -2.3 (a number)
(-2.34).toFixed(1); // "-2.3" (a string)
Using toFixed
in real code
Use toFixed
whenever you’re presenting numbers to a human and you need a fixed number of decimals. Remember that it returns a string; if you need to do math afterwards, convert it back to a number with Number(...)
, parseFloat(...)
, or the unary +
operator.
const price = 19.9;
const label = price.toFixed(2); // "19.90" — perfect for UI
// If you must compute with it again:
const numeric = Number(label); // 19.9
If you need locale-aware separators (like ,
vs .
or grouping), format with toLocaleString
instead. Use toFixed
for strict control of fractional digits, and use toLocaleString
for human-friendly formatting in a specific locale.
(12345.6).toFixed(2); // "12345.60"
(12345.6).toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
// "12.345,60"
Converting inputs and guarding against surprises
Parse string inputs first, then fix:
function toMoney(value) {
const n = Number.parseFloat(value);
if (!Number.isFinite(n)) return "—";
return n.toFixed(2);
}
toMoney("1.2"); // "1.20"
toMoney("1.2e3"); // "1200.00"
toMoney("abc"); // "—"
For calculations where tiny rounding differences matter (for example, financial back-end logic), consider doing the math in integers (cents) and apply toFixed
only at the edge when presenting to users:
// Work in cents internally
const subtotalCents = 1995; // €19.95
const taxCents = Math.round(subtotalCents * 0.25); // 25% VAT
const total = (subtotalCents + taxCents) / 100;
total.toFixed(2); // presentation string like "24.94"
toFixed
vs. toPrecision
, toExponential
, and toString
toFixed(d)
locks in exactly d
digits after the decimal. toPrecision(p)
targets a total number of significant digits, automatically choosing fixed or exponential form based on the value and requested precision. toExponential
always uses exponential notation, while toString
picks the shortest accurate representation. The spec precisely defines the allowed ranges and the rounding behavior for all of these methods.
const x = 1234.567;
x.toFixed(2); // "1234.57" // fixed digits after decimal
x.toPrecision(3); // "1.23e+3" // 3 significant digits -> exponential
x.toExponential(2); // "1.23e+3" // explicitly exponential
x.toString(); // "1234.567" // shortest accurate form
Debugging tips and patterns
When a toFixed
result looks “off”, it’s almost always floating-point representation shining through. Verify the raw binary approximation or inspect more digits to see which way it should round; the MDN examples illustrate exactly this behavior with 2.55
. If toFixed
throws, double-check that your digits
is an integer in the [0, 100]
range. And if -0.00
is not acceptable in your UI, post-process the string as shown above.
Reference examples to keep handy
// Basic formatting
(0.004).toFixed(2); // "0.00"
(123.456).toFixed(2); // "123.46"
(123).toFixed(2); // "123.00"
// Boundary values
(0.005).toFixed(2); // "0.01"
(9999999999999999).toFixed(0) // "10000000000000000"
// Signed zeros
(-0.0004).toFixed(3); // "-0.000"
(-0.0004).toFixed(3).replace(/^-0\./, "0."); // "0.000"
// Locale vs fixed
(12345.6).toFixed(2); // "12345.60"
(12345.6).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
// "12,345.60"
// Guarding digits
function safeFixed(x, digits = 2) {
const d = Math.trunc(digits);
if (d < 0 || d > 100) throw new RangeError("digits out of range");
return Number(x).toFixed(d);
}
References
Source | What it covers |
---|---|
MDN: Number.prototype.toFixed() | Behavior overview, examples, limits, large-number notes, negative-number precedence, precision caveats. |
ECMAScript® 2026 Language Specification — Number.prototype.toFixed | Authoritative algorithm: argument conversion, [0,100] range, rounding rule (tie picks larger n ), sign handling, exponential fallback threshold. |
MDN: RangeError — precision is out of range | The RangeError thrown by toFixed and related methods when precision arguments are out of bounds. |
MDN: Number.prototype.toString() | Relation between toFixed and toString for very large magnitudes and string forms. |
MDN: Math.sign() | Evidence of IEEE-754 signed zero in JavaScript, relevant to "-0.00" outcomes. |