Convert a number to a string by specifying the number of significant digits (toPrecision)

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

SourceWhat it covers
MDN: Number.prototype.toPrecision()Definition, syntax, examples, exponent switching thresholds, precision range, and exceptions.
ECMAScript® 2026: Number.prototype.toPrecisionFormal 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 rangeThe 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.