When you want a numeric value as a string with a specific count of significant digits—not “decimal places” but meaningful digits overall—reach for Number.prototype.toPrecision
. It returns a string, rounding as needed, and chooses fixed or scientific notation automatically based on the number’s magnitude and the precision you ask for. In other words, you tell JavaScript how many total digits matter, and it formats the value accordingly.
// Say you want 3 significant digits everywhere:
(1234.567).toPrecision(3); // "1.23e+3"
(0.001234567).toPrecision(3) // "0.00123"
(12.34567).toPrecision(3) // "12.3"
The essentials
toPrecision(precision)
returns a string with exactly precision
significant digits. If you omit the argument, it behaves like toString()
and doesn’t enforce a precision. The allowed range for precision
is 1 to 100 inclusive; anything outside throws RangeError
. If the number is NaN
, Infinity
, or -Infinity
, it simply returns that token as a string.
(42).toPrecision(); // "42" ← same as (42).toString()
(42).toPrecision(1); // "4e+1"
(42).toPrecision(5); // "42.000"
Number(1/0).toPrecision(4); // "Infinity"
Number.NaN.toPrecision(4); // "NaN"
Significant digits vs decimal places
Significant digits count the digits that carry meaning, starting from the first non-zero digit. That’s why a small number can produce very different shapes compared to a large number. With precision 4:
(987654).toPrecision(4); // "9.877e+5" ← 4 total digits
(0.000987654).toPrecision(4); // "0.0009877" ← still 4 total digits
The representation may be in fixed or exponential notation. Engines typically switch to exponential notation when the exponent is ≥ the requested precision or < −6, which is why large or tiny values flip to e±n
form at certain thresholds.
Rounding and trailing zeros
Rounding follows normal rules. If rounding inflates a leading digit (like 9.999
with low precision), the exponent or integer part adjusts.
(9.999).toPrecision(3); // "10.0"
(0.009996).toPrecision(3) // "0.0100"
Trailing zeros are significant in the string output. They’re included to reach the requested count of significant digits, and they can appear either after the decimal point or inside exponential notation.
(123).toPrecision(6); // "123.000" ← fixed with padding
(1.23e5).toPrecision(6); // "123000" ← fixed, no exponent needed
(1.23e5).toPrecision(3); // "1.23e+5" ← exponent form at lower precision
How it compares to toFixed
, toExponential
, and toString
Use toPrecision
when you care about total significant digits. Use toFixed
when you care about decimal places after the point. Use toExponential
to force scientific notation. If you omit the precision entirely, toPrecision()
defers to toString()
—useful when you sometimes want raw output and other times want a specific precision.
const n = 1234.567;
// Significant digits (total):
n.toPrecision(4); // "1235" (4 sig figs)
// Decimal places:
n.toFixed(2); // "1234.57" (2 places after decimal)
// Always exponent:
n.toExponential(2); // "1.23e+3" (2 places after the decimal in mantissa)
// Raw-ish representation:
n.toPrecision(); // same as n.toString()
Choosing a precision
Pick a precision that matches the problem domain. Three to five significant digits are common for UI displays; scientific/financial work may require more. The standard allows up to 100, which is plenty beyond the ~16 digits of binary64 precision—useful when you want formatting with extra trailing zeros for alignment, not extra accuracy beyond IEEE-754’s real precision. Range validation and the upper bound are specified by ECMAScript.
// UI-friendly compact readout:
(0.00034898).toPrecision(3); // "0.000349"
// Engineering readout:
(123456789).toPrecision(6); // "1.23457e+8"
// Formatting-only padding (doesn't add real precision):
(12.34).toPrecision(10); // "12.34000000"
Edge cases you’ll meet in the wild
If you call toPrecision
on a Number object (e.g., new Number(123)
), it still works because the method is defined on Number.prototype
, but calling it on a non-number object will throw TypeError
since the method isn’t generic.
new Number(123).toPrecision(3); // "123"
Number.prototype.toPrecision.call(123, 3); // "123"
Number.prototype.toPrecision.call({}, 3); // TypeError
If the precision is out of range—less than 1 or greater than 100—you’ll get a RangeError
. That same class of error also occurs with toFixed
/toExponential
when their digit arguments are out of range.
(5).toPrecision(0); // RangeError: precision is out of range
(5).toPrecision(101); // RangeError
When the value is zero (positive or negative), the sign is preserved and the output pads zeros to the requested significant digits. The spec defines zero handling explicitly.
(+0).toPrecision(4); // "0.000"
(-0).toPrecision(4); // "-0.000"
Practical patterns
You’ll often want to ensure a consistent width of numeric strings, especially in logs, tables, or telemetry. Because toPrecision
includes trailing zeros to hit the target significant digits, it’s a useful way to produce aligned text columns without manual padding.
const values = [0.0012345, 0.12345, 12.345, 1234.5];
for (const v of values) {
console.log(v.toPrecision(5));
}
// 0.0012345
// 0.12345
// 12.345
// 1234.5
To show a number with at most N significant digits, but avoid exponent notation for “friendly” ranges, you can post-process by checking for e
and adjusting, or choose a precision high enough that the switch never happens for your expected magnitude window.
function friendlyPrecision(x, p = 6) {
const s = x.toPrecision(p);
return s.includes('e') ? Number(x).toString() : s;
}
friendlyPrecision(123456, 4); // "123456" (falls back to raw)
friendlyPrecision(0.000012345, 4); // "0.000012345"
Testing your understanding
Try predicting before you run these. They hit rounding boundaries, notation switches, and padding.
(999.95).toPrecision(3); // ?
(0.000099995).toPrecision(2) // ?
(1.005).toPrecision(3) // ?
(1.2345e-7).toPrecision(1) // ?
(1.2345e-7).toPrecision(10) // ?
Expected results:
"1.00e+3" // rounds up, exponent bump
"0.00010" // fixed, 2 sig figs with padding
"1.01" // classic rounding
"1e-7" // exponent at low precision
"1.234500000e-7" // exponent with padding
The exponent switching and trailing-zero behavior are the key reasons these look the way they do.
Reference
Source | What it covers |
---|---|
MDN: Number.prototype.toPrecision() | Definition, syntax, examples, exponent switching thresholds, precision range, and exceptions. |
ECMAScript® 2026: Number.prototype.toPrecision | Formal algorithm: ToString fallback when precision is undefined , allowed range 1–100, handling of finite vs non-finite values, sign handling for zero. |
MDN: RangeError — precision is out of range | The RangeError thrown by toPrecision /toFixed /toExponential when arguments are outside allowed ranges. |
MDN: Number.prototype.toFixed() | Contrast with toPrecision : decimal places vs significant digits. |
MDN: Number.prototype.toString() | How raw number-to-string conversion differs and why toPrecision() can be “more precise” for display. |