Skip to content

roundToFractionDigits function ​

Rounds a number to a number of decimals, half away from zero, with plain arithmetic: the same result as Number(formatDecimal(value, maxFractionDigits)), much faster. Never returns -0.

ts
import { roundToFractionDigits } from 'utils';

Signature ​

ts
export function roundToFractionDigits(value: number, maxFractionDigits = 3): number

Parameters ​

NameDescription
valueThe number to round; NaN and infinities come back unchanged.
maxFractionDigitsThe number of decimals to keep. Defaults to 3.

Returns ​

The rounded number.

Example ​

ts
roundToFractionDigits(1.005, 2); // 1.01 (`toFixed` gives 1.00)
roundToFractionDigits(-2.5, 0); // -3

Tests ​

Line coverage: 100 %.

  • ✅ rounds 1.005 to 2 digits as 1.01
  • ✅ rounds 1.004 to 2 digits as 1
  • ✅ rounds -1.005 to 2 digits as -1.01
  • ✅ rounds 2.5 to 0 digits as 3
  • ✅ rounds -2.5 to 0 digits as -3
  • ✅ rounds 0.285 to 2 digits as 0.29
  • ✅ rounds 1.45 to 1 digits as 1.5
  • ✅ rounds 123.456 to 1 digits as 123.5
  • ✅ rounds 1e+21 to 2 digits as 1e+21
  • ✅ rounds 1e-7 to 8 digits as 1e-7
  • ✅ rounds 1.5 to 150 digits as 1.5
  • ✅ never returns -0
  • ✅ returns non-finite values unchanged
  • ✅ matches formatDecimal on decimal inputs
  • ✅ takes the defaults

Source ​

src/math/round-to-fraction-digits.ts (on GitHub)

Source code
ts
const DECIMAL_BASE = 10;
const TABLE_SIZE = 101;
const POWERS_OF_TEN: readonly number[] = /* @__PURE__ */ Array.from(
  { length: TABLE_SIZE },
  (_, exponent) => DECIMAL_BASE ** exponent,
);
const MAX_EXACT_INTEGER = Number.MAX_SAFE_INTEGER + 1;

/**
 * Rounds a number to a number of decimals, half away from zero, with plain arithmetic: the same result as
 * `Number(formatDecimal(value, maxFractionDigits))`, much faster. Never returns `-0`.
 *
 * @param value - The number to round; `NaN` and infinities come back unchanged.
 * @param maxFractionDigits - The number of decimals to keep. Defaults to `3`.
 * @returns The rounded number.
 * @example
 * roundToFractionDigits(1.005, 2); // 1.01 (`toFixed` gives 1.00)
 * roundToFractionDigits(-2.5, 0); // -3
 */
export function roundToFractionDigits(value: number, maxFractionDigits = 3): number {
  const factor = POWERS_OF_TEN[maxFractionDigits] ?? DECIMAL_BASE ** maxFractionDigits;
  const scaled = Math.abs(value) * factor;
  if (!Number.isFinite(scaled) || scaled >= MAX_EXACT_INTEGER) {
    return value;
  }
  // `1 + EPSILON` absorbs the binary error: 1.005 is stored as 1.00499999999999989…
  const rounded = Math.round(scaled * (1 + Number.EPSILON)) / factor;
  return rounded !== 0 && value < 0 ? -rounded : rounded;
}
Test file
ts
import { formatDecimal } from '../format';
import { roundToFractionDigits } from './round-to-fraction-digits';

describe(roundToFractionDigits, () => {
  it.for([
    [1.005, 2, 1.01],
    [1.004, 2, 1],
    [-1.005, 2, -1.01],
    [2.5, 0, 3],
    [-2.5, 0, -3],
    [0.285, 2, 0.29],
    [1.45, 1, 1.5],
    [123.456, 1, 123.5],
    [1e21, 2, 1e21],
    [0.0000001, 8, 0.0000001],
    [1.5, 150, 1.5],
  ] as const)('rounds %s to %s digits as %s', ([value, digits, expected]) => {
    expect(roundToFractionDigits(value, digits)).toBe(expected);
  });

  it('never returns -0', () => {
    expect(Object.is(roundToFractionDigits(-0.001, 2), 0)).toBe(true);
    expect(Object.is(roundToFractionDigits(-0, 2), 0)).toBe(true);
  });

  it('returns non-finite values unchanged', () => {
    expect(roundToFractionDigits(NaN, 2)).toBeNaN();
    expect(roundToFractionDigits(Infinity, 2)).toBe(Infinity);
  });

  it('matches formatDecimal on decimal inputs', () => {
    const mismatches: string[] = [];
    for (let digits = 0; digits <= 4; digits++) {
      for (let thousandths = -20_000; thousandths <= 20_000; thousandths += 7) {
        const value = thousandths / 1000;
        const expected = Number(formatDecimal(value, digits));
        if (roundToFractionDigits(value, digits) !== expected) {
          mismatches.push(`${value} (${digits} digits)`);
        }
      }
    }
    expect(mismatches).toStrictEqual([]);
  });

  it('takes the defaults', () => {
    expect(roundToFractionDigits(Math.PI)).toStrictEqual(roundToFractionDigits(Math.PI, 3));
  });
});

Keywords: round, to, fraction, digits, math, function

Generated from the JSDoc of the sources.