In JavaScript, `(1.005).toFixed(2)` returns "1.00" rather than "1.01". Why, and what else should you know about `Number.prototype.toFixed`?
answer
- it rounds the stored double
- the literal was never exactly that
- 1.00499999999999989
- ties go away from zero
- the return value is a string
basics
~20 sThe double stored for the literal 1.005 is 1.00499999999999989, just under the halfway point, so toFixed rounds it down. toFixed rounds the stored binary value, not the decimal you typed, and it returns a string.
solid answer
~40 s`toFixed` rounds the double that is actually in memory, and the literal `1.005` is stored as 1.00499999999999989342 — genuinely below the halfway point — so rounding to two places correctly yields `"1.00"`. The same trap hits `(2.675).toFixed(2)`, which gives `"2.67"`, and `(1.45).toFixed(1)`, which gives `"1.4"`. Where a value *is* an exact tie, `toFixed` rounds the magnitude away from zero: `(2.5).toFixed(0)` is `"3"` and `(-2.5).toFixed(0)` is `"-3"`, which differs from `Math.round(-2.5)`, whose ties go toward positive infinity and give -2. Two more things bite people: `toFixed` returns a *string*, so `+` concatenates unless you convert back; and for magnitudes at or above 1e21 it returns exponential notation such as `"1e+21"` instead of a fixed-point string. It is a formatter, not a way to make inexact arithmetic exact.
code
javascript · 10 linesconsole.log((1.005).toFixed(20)); // "1.00499999999999989342"
console.log((1.005).toFixed(2)); // "1.00"
console.log((1.55).toFixed(1)); // "1.6" - stored slightly high
console.log((1.45).toFixed(1)); // "1.4" - stored slightly low
console.log((2.5).toFixed(0), (-2.5).toFixed(0)); // "3" "-3"
console.log(Math.round(2.5), Math.round(-2.5)); // 3 -2
console.log(typeof (1.5).toFixed(2)); // "string"
console.log((1e21).toFixed(2)); // "1e+21"go deeper
Remember that toFixed returns a string and that its rounding follows the stored binary value, so a literal like 1.005 can round the way you did not expect. Convert with Number() before doing arithmetic again.
Explain the mechanism: the literal rounds to a double slightly below the halfway point, toFixed rounds that value, and only genuine ties reach the away-from-zero rule that separates it from Math.round on negatives.
Show where this reaches production — invoice lines that fail to reconcile, thresholds crossed inconsistently, parsers that assume a decimal point and meet "1e+21" — and place rounding as one explicit, tested step over exact integers rather than a formatting call in the middle of a pipeline.
Own the rounding policy across the system: which rule applies (half-up, half-even, per-jurisdiction), where the single rounding point sits, and how the same rule is enforced in every service and stored artifact so that two components never disagree about the last cent.
## What toFixed is for `Number.prototype.toFixed(digits)` produces a string with exactly `digits` digits after the decimal point. It is a *presentation* function: given a double, it picks the decimal string of that fixed length whose value is closest to the double, breaking exact ties by choosing the larger magnitude. Everything surprising about it follows from that one sentence, plus the fact that the double it starts from is usually not the decimal number you wrote. ## The 1.005 case There is no tie to break here at all: ```js (1.005).toFixed(20); // "1.00499999999999989342" (1.005).toFixed(2); // "1.00" ``` The literal `1.005` cannot be represented exactly in binary, and the nearest double happens to sit just *below* one and a half hundredths. Rounding that value to two decimals genuinely gives 1.00. `toFixed` is behaving correctly; the information was lost when the literal was parsed. Others in the same family: ```js (2.675).toFixed(2); // "2.67" (1.45).toFixed(1); // "1.4" (1.55).toFixed(1); // "1.6" - this one is stored slightly high (8.575).toFixed(2); // "8.57" ``` Note the third line: the direction is not systematic. Whether a given decimal lands above or below the halfway point depends on the binary expansion, so you cannot memorise "toFixed rounds down". Some go each way. The common workaround has the same disease: ```js Math.round(1.005 * 100) / 100; // 1, not 1.01 ``` `1.005 * 100` is 100.49999999999999, so `Math.round` returns 100. Multiplying by a power of ten does not recover a value that was never stored. ## Tie-breaking, when there is a real tie Values like 2.5 and 0.5 *are* exactly representable, so `toFixed` faces a genuine tie. The algorithm works on the magnitude and takes the larger candidate, then reattaches the sign: ```js (2.5).toFixed(0); // "3" (0.5).toFixed(0); // "1" (-2.5).toFixed(0); // "-3" Math.round(-2.5); // -2 ``` So `toFixed` is round-half-away-from-zero, while `Math.round` is round-half-toward-positive-infinity. They disagree on every negative tie. Neither is banker's rounding (round-half-to-even), which some financial rules require and which you would have to implement yourself. ## It returns a string ```js typeof (1.5).toFixed(2); // "string" (1.5).toFixed(2) + 1; // "1.501" ``` This is a routine source of bugs when a formatted value flows back into arithmetic. If you need a number again, convert explicitly with `Number(...)` — and accept that the round trip re-introduces a double: `Number((0.1 + 0.2).toFixed(2))` is 0.3, which is fine for display but does not make the underlying pipeline exact. ## The edges - `digits` must be between 0 and 100, otherwise `toFixed` throws a `RangeError`. (Older engines allowed only 0 to 20.) - For a magnitude of 1e21 or greater, `toFixed` gives up on fixed notation and returns the same string as `toString`: `(1e21).toFixed(2)` is `"1e+21"`. Code that assumes a decimal point is always present will mis-parse it. - `toFixed` is not locale-aware: the separator is always `.` and there is no grouping. Presenting a number to a user in their own locale is a different job with different tools. ## toPrecision is a different question `toPrecision(p)` keeps *p significant digits*, not *p decimals*, and switches to exponential notation when the exponent falls outside the range that fixed notation can express: ```js (1234.5678).toPrecision(6); // "1234.57" (123456).toPrecision(2); // "1.2e+5" (0.00001234).toPrecision(2);// "0.000012" ``` It shares `toFixed`'s core property — it rounds the stored double — so it inherits the same surprises. ## The rule to take away Use `toFixed` to render a value you already trust. Do not use it as a rounding *engine* in a calculation pipeline, and never as a fix for accumulated floating-point drift: it operates on a value that is already off, so it can only round the wrong number to a tidy shape. Where the domain demands exact decimal behaviour, hold the value in integer minor units, apply your own explicit rounding rule where a division forces one, and let `toFixed` do nothing but insert a decimal point at the very end.
- How does toFixed's tie-breaking differ from Math.round's?`toFixed` works on the magnitude and rounds exact ties away from zero, so `(2.5).toFixed(0)` is `"3"` and `(-2.5).toFixed(0)` is `"-3"`. `Math.round` breaks ties toward positive infinity, so `Math.round(-2.5)` is -2. They agree on positive ties and disagree on every negative one. Neither implements banker's rounding.
- What does (1e21).toFixed(2) return, and why does that matter?It returns `"1e+21"`. At magnitudes of 1e21 and above `toFixed` falls back to the same output as `toString` rather than emitting a fixed-point string. Any code that assumes the result contains a decimal point — string slicing, a regex, a naive parser downstream — breaks on that input rather than merely formatting oddly.
- Is Math.round(x * 100) / 100 a safe substitute for toFixed(2)?No, it has the identical flaw. `1.005 * 100` is 100.49999999999999, so `Math.round` yields 100 and the result is 1, not 1.01. Scaling by a power of ten cannot recover precision the literal never had, and the multiplication can add its own rounding. If exactness matters, keep the value in integer minor units from the start.
saying these in an interview costs you the question
- Says toFixed always rounds down
- Believes toFixed makes floating-point arithmetic exact
- Treats the return value as a number
- Assumes toFixed uses banker's rounding
- Thinks Math.round(x*100)/100 avoids the problem