Round to PrecisionLoading saved progress…

Round to Precision

Rounding a number to N decimal places means snapping it to the nearest value on a grid of that fineness — 1.005 to two places is 1.01. The obvious one-liner, Math.round(value * 100) / 100, quietly returns the wrong answer for that exact case, because 1.005 cannot be stored precisely in binary floating point and the stray multiply drifts it the wrong way. You will build roundToPrecision = { round, floor, ceil }, three functions that round to a given number of places correctly, in every direction.

Signature

const roundToPrecision: {
  round(value: number, digits?: number): number; // digits defaults to 0
  floor(value: number, digits?: number): number;
  ceil(value: number, digits?: number): number;
};

Examples

roundToPrecision.round(1.005, 2);   // 1.01   (the classic — naive Math.round gives 1)
roundToPrecision.round(1234.5, -2); // 1200   (negative digits round to hundreds)
roundToPrecision.floor(-1.15, 1);   // -1.2   (floor goes down, toward -Infinity)
roundToPrecision.ceil(-1.15, 1);    // -1.1   (ceil goes up, toward +Infinity)
roundToPrecision.round(2.4);        // 2      (digits defaults to 0)
roundToPrecision.round(1e-8, 2);    // 0      (exponent-notation input is fine)
roundToPrecision.round(Infinity);   // Infinity (non-finite values pass through)

Notes

  • digits defaults to 0 — positive digits keep decimals, 0 rounds to an integer, and negative digits round to tens, hundreds, and beyond (round(15, -1) is 20).
  • Half-up roundinground follows Math.round: ties go toward positive infinity, so round(2.5) is 3 and round(-2.5) is -2.
  • floor and ceil follow the number linefloor always moves toward negative infinity and ceil toward positive infinity, which for negatives means floor(-1.15, 1) is -1.2 and ceil(-1.15, 1) is -1.1.
  • Avoid the float trap — do not write Math.round(value * 10 ** digits) / 10 ** digits. For value = 1.005 the product is 100.4999999..., which rounds the wrong way. Shift the decimal point without multiplying instead.
  • Non-finite inputs pass through — return NaN, Infinity, and -Infinity unchanged; you do not round them.
  • Assume numeric inputvalue is always a number and digits an integer; you do not need to validate types or reject strings.

FAQ

Why does Math.round(1.005 * 100) / 100 return 1 instead of 1.01?
Because 1.005 has no exact binary representation — the nearest double is about 1.00499999999999989, so 1.005 * 100 evaluates to 100.49999999999999, which Math.round sends down to 100 and back to 1.00. Shifting the decimal point in the string domain avoids computing that stray product.
Why shift the decimal point through a string instead of multiplying by a power of ten?
Number('1.005e2') parses the exact decimal 100.5, while 1.005 * 100 first computes a slightly-off product. Building the shifted string from String(value) sidesteps the multiply, and with it the rounding error, entirely.
How do floor and ceil behave on negative numbers?
floor always moves toward negative infinity and ceil toward positive infinity, so floor(-1.15, 1) is -1.2 (further from zero) and ceil(-1.15, 1) is -1.1 (closer to zero). This mirrors Math.floor and Math.ceil exactly.
Loading editor…