Skip to the code
cf.

Format a number

Write formatNumber(value, options) without Intl or toLocaleString. It formats a number for display with a thousands separator and a fixed number of decimals.

options is optional, and so is each of its properties: decimals (default 0), thousands (the group separator, default ","), and decimal (the decimal point, default ".").

Round to exactly decimals digits after the point, adding zeros when needed (7 with 2 decimals is 7.00), and with no point at all when decimals is 0. Halves round away from zero: 2.5 becomes 3, and -2.5 becomes -3.

Split the whole-number part into groups of three digits, counting from the right, with thousands between groups: 1234567 is 1,234,567, and 999 has no separator. The digits after the point are never grouped.

Negative numbers start with -. A number that rounds to zero has no minus sign: -0.4 is 0, not -0. You can assume value is finite and smaller than 1e21 in absolute value.

formatNumber(1234567.891, { decimals: 2 }); // "1,234,567.89"
formatNumber(-1234.5);                      // "-1,235"
formatNumber(0.5, { decimals: 3 });         // "0.500"
formatNumber(1234567.891, { decimals: 2, thousands: ".", decimal: "," });
// "1.234.567,89"

What the tests check

  1. groups the whole part in threes from the right
    expect(formatNumber(1234567)).toBe("1,234,567");
    expect(formatNumber(1000)).toBe("1,000");
    expect(formatNumber(999)).toBe("999");
    expect(formatNumber(0)).toBe("0");
    expect(formatNumber(12345678901)).toBe("12,345,678,901");
    
  2. rounds to the given decimals, padding with zeros
    expect(formatNumber(3.14159, { decimals: 3 })).toBe("3.142");
    expect(formatNumber(1234.5, { decimals: 2 })).toBe("1,234.50");
    expect(formatNumber(7, { decimals: 1 })).toBe("7.0");
    expect(formatNumber(1234.56)).toBe("1,235");
    
  3. rounds halves away from zero
    expect(formatNumber(2.5)).toBe("3");
    expect(formatNumber(-2.5)).toBe("-3");
    expect(formatNumber(0.125, { decimals: 2 })).toBe("0.13");
    expect(formatNumber(-0.125, { decimals: 2 })).toBe("-0.13");
    
  4. never groups the digits after the point
    expect(formatNumber(1234.56789, { decimals: 5 })).toBe("1,234.56789");
    expect(formatNumber(0.123456, { decimals: 6 })).toBe("0.123456");
    
  5. puts a minus sign in front of negative numbers
    expect(formatNumber(-1234567.891, { decimals: 2 })).toBe("-1,234,567.89");
    expect(formatNumber(-123)).toBe("-123");
    expect(formatNumber(-123456)).toBe("-123,456");
    
  6. never shows -0
    expect(formatNumber(-0.4)).toBe("0");
    expect(formatNumber(-0.001, { decimals: 2 })).toBe("0.00");
    expect(formatNumber(-0)).toBe("0");
    
  7. uses the given separators
    expect(formatNumber(1234567.891, { decimals: 2, thousands: ".", decimal: "," })).toBe("1.234.567,89");
    expect(formatNumber(1234567, { thousands: " " })).toBe("1 234 567");
    expect(formatNumber(1234.5, { decimals: 1, thousands: "" })).toBe("1234.5");
    expect(formatNumber(1234.5, { decimals: 1, decimal: "," })).toBe("1,234,5");
    
  8. works without Intl or toLocaleString
    const toLocaleString = Number.prototype.toLocaleString;
    const NumberFormat = Intl.NumberFormat;
    Number.prototype.toLocaleString = () => {
      throw new Error("formatNumber must not use toLocaleString");
    };
    Intl.NumberFormat = function () {
      throw new Error("formatNumber must not use Intl.NumberFormat");
    };
    let results;
    try {
      results = [
        formatNumber(1234567.891, { decimals: 2 }),
        formatNumber(-9876543.21, { decimals: 1, thousands: " ", decimal: "," }),
      ];
    } finally {
      // Put the built-ins back even when formatNumber throws, before expect runs.
      Number.prototype.toLocaleString = toLocaleString;
      Intl.NumberFormat = NumberFormat;
    }
    expect(results).toEqual(["1,234,567.89", "-9 876 543,2"]);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).