JavaScript toFixed Returns a String: Fix Addition Before Formatting
Contents
(1.25).toFixed(2) + (2.5).toFixed(2) produces '1.252.50'. Each formatted value is a string, so + joins them. Keep values numeric while calculating, then call toFixed(2) on the final result when you need display text.
Converting the formatted string back to a number can restore arithmetic, but it also discards trailing zeros and preserves any rounding already applied. The regression fixture below distinguishes those three decisions.
Put formatting after the calculation
Number.prototype.toFixed returns text. For these inputs, the calculation should be (1.25 + 2.5).toFixed(2), which returns '3.75'. Store that result in a display field rather than overwriting the numeric inputs.
If your inputs arrive as strings, validate and convert them before calculating. Do not apply parseInt to formatted decimal amounts: it discards the fractional part. The separate integer conversion guide explains why prefix parsing and complete numeric conversion have different acceptance rules.
| Required result | Operation | Consequence |
|---|---|---|
| Numeric sum | a + b with numeric operands | The result remains available for arithmetic. |
| Two-place display text | (a + b).toFixed(2) | The result is a string. |
| Number from existing formatted text | Number(text) after validating the input contract | Trailing zeros are not retained as display metadata. |
| Total of individually rounded items | Convert each formatted item, then sum | Can differ from rounding the original total. |
The last row is a business rule, not an interchangeable implementation of the second row. Decide where rounding belongs before changing the pipeline.
Run a regression fixture
Save this as tofixed-check.mjs and run node tofixed-check.mjs. These assertions were executed during article preparation on macOS with Node.js 24.13.0. They use finite numeric literals and built-in formatting; they do not implement a currency accounting policy.
import assert from 'node:assert/strict';
const a = 1.25;const b = 2.5;assert.equal(typeof a.toFixed(2), 'string');assert.equal(a.toFixed(2) + b.toFixed(2), '1.252.50');assert.equal((a + b).toFixed(2), '3.75');assert.equal(Number(a.toFixed(2)) + Number(b.toFixed(2)), 3.75);console.log('addition: concatenation reproduced; numeric sum passed');
const display = (2.5).toFixed(2);assert.equal(display, '2.50');assert.equal(Number(display), 2.5);assert.equal(String(Number(display)), '2.5');console.log('display: string keeps trailing zero; number does not');
const items = [0.125, 0.125];const roundedItemsTotal = items.reduce( (sum, item) => sum + Number(item.toFixed(2)), 0,);const originalTotal = items.reduce((sum, item) => sum + item, 0);assert.equal(roundedItemsTotal.toFixed(2), '0.26');assert.equal(originalTotal.toFixed(2), '0.25');console.log('rounding: per-item=0.26; final-total=0.25');
assert.equal((1.005).toFixed(2), '1.00');assert.equal((1e21).toFixed(2), '1e+21');assert.equal((Infinity).toFixed(2), 'Infinity');assert.throws(() => (1).toFixed(101), RangeError);console.log('boundaries: binary rounding, large values, infinity, digits passed');Expected output in that environment:
addition: concatenation reproduced; numeric sum passeddisplay: string keeps trailing zero; number does notrounding: per-item=0.26; final-total=0.25boundaries: binary rounding, large values, infinity, digits passedThe 0.125 inputs make the rounding-stage difference visible without relying on an approximate representation of that input. Rounding each item produces two 0.13 values; summing the original items produces 0.25. Converting the rounded strings back cannot recover the discarded precision.
Keep the boundary explicit
The ECMAScript algorithm formats the Number value already stored. It does not recover an exact decimal value from the original source text. The 1.005 assertion demonstrates why a formatting method alone is insufficient to promise a particular decimal rounding policy.
For absolute values at least 1e21, formatting can use exponential notation. Non-finite inputs can produce text such as 'Infinity'; asking for two places does not validate the input. Validate finiteness separately when your application requires it. The supported digits range is 0 through 100; the fixture checks rejection above that range.
Browser engines, locale formatting, external form input, and a decimal arithmetic library were not tested here. For accounting rules, define the unit, rounding stage, and tie rule separately and verify the chosen implementation against those requirements.
Keep the arithmetic result and display string separate. The regression should prove both that addition stays numeric and that rounding happens at the stage your application intends.
References: