Elias Zamaria
Elias Zamaria

Reputation: 101153

How to format a number with commas as thousands separators?

I am trying to print an integer in JavaScript with commas as thousands separators. For example, I want to show the number 1234567 as "1,234,567". How would I go about doing this?

Here is how I am doing it:

function numberWithCommas(x) {
    x = x.toString();
    var pattern = /(-?\d+)(\d{3})/;
    while (pattern.test(x))
        x = x.replace(pattern, "$1,$2");
    return x;
}

console.log(numberWithCommas(1000))

Is there a simpler or more elegant way to do it? It would be nice if it works with floats also, but that is not necessary. It does not need to be locale-specific to decide between periods and commas.

Upvotes: 2763

Views: 2280012

Answers (30)

Tariqul Islam
Tariqul Islam

Reputation: 103

var number = 1234567;
var formattedNumber = number.toLocaleString();

console.log(formattedNumber); // Output: 1,234,567
If you want to force a specific locale or customize the formatting options, you can pass them as arguments to the toLocaleString method.

var number = 1234567;
var formattedNumber = number.toLocaleString('en-US', { style: 'decimal' });

console.log(formattedNumber); // Output: 1,234,567

Upvotes: 7

Walulya francis
Walulya francis

Reputation: 583

var floatingNumber = 1000000.06665656676709;
var doubleNumber = floatingNumber.toFixed(2);
var athousand = parseFloat(doubleNumber).toLocaleString();
document.write(athousand);

Upvotes: 5

vsync
vsync

Reputation: 130550

Below are two different browser APIs that can transform Numbers into structured Strings. Keep in mind that not all users' machines have a locale that uses commas in numbers. To enforce commas to the output, any "western" locale may be used, such as en-US

let number = 1234567890; // Example number to be converted

⚠️ Mind that javascript has a maximum integer value of 9007199254740991


toLocaleString

// default behaviour on a machine with a locale that uses commas for numbers
let number = 1234567890;
number.toLocaleString(); // "1,234,567,890"

// With custom settings, forcing a "US" locale to guarantee commas in output
let number2 = 1234.56789; // floating point example
number2.toLocaleString('en-US', {maximumFractionDigits:2}); // "1,234.57"

//You can also force a minimum of 2 trailing digits
let number3 = 1.5;
number3.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2}); //"1.50"

NumberFormat

let number = 1234567890;
let nf = new Intl.NumberFormat('en-US');
nf.format(number); // "1,234,567,890"

From what I checked (Firefox at least) they are both more or less same regarding performance.

Live demo: https://codepen.io/vsync/pen/MWjdbgL?editors=1000

Upvotes: 576

Hasan Shouman
Hasan Shouman

Reputation: 2372

I have found this answer somewhere earlier , and I updated it to allow negative numbers.

You can use it after converting the number to string.

The removal of the additional decimal places is just for convenience as it is a very common situation.You can skip it if not needed.

// Keep only digits, hyphen  and decimal points:
             myNum.toString()   .replace(/[^-\d.]/g, "")
                // Remove duplicated decimal point, if one exists:
                .replace(/^(\d*\.)(.*)\.(.*)$/, '$1$2$3')
                // Keep only two digits past the decimal point:
                .replace(/\.(\d{2})\d+/, '.$1')
                // Add thousands separators:
                .replace(/\B(?=(\d{3})+(?!\d))/g, ",")

Upvotes: -1

doctorgu
doctorgu

Reputation: 634

I challenge with another solution.

First code was this.

function formatNumber(number) {
  if (number < 1000) {
    return String(number);
  }
  if (number < 1000000) {
    let numbers = String(number).split('');
    numbers.splice(-3, 0, ',');
    return numbers.join('');
  }
  if (number < 1000000000) {
    let numbers = String(number).split('');
    numbers.splice(-3, 0, ',');
    numbers.splice(-7, 0, ',');
    return numbers.join('');
  }
  if (number < 1000000000000) {
    let numbers = String(number).split('');
    numbers.splice(-3, 0, ',');
    numbers.splice(-7, 0, ',');
    numbers.splice(-11, 0, ',');
    return numbers.join('');
  }

  throw new Error(`number: ${number} is too big`);
}

This code is lengthy and not scalable, but it is effective because it checks number itself not length of string that was converted from number.

So I converted to neat code based on this idea. (You can make 12 more bigger to prevent error.)

function formatNumber(number) {
  let commas = -1;
  for (let n3 = 3; n3 <= 12; n3 += 3) {
    commas++;
    const max = Math.pow(10, n3);
    if (number < max) {
      let numbers = String(number).split('');
      for (let i = 0; i < commas; i++) {
        numbers.splice(-(3 * (i + 1) + i), 0, ',');
      }
      return numbers.join('');
    }
  }

  throw new Error(`number: ${number} is too big`);
}

Upvotes: 0

Maor Ben
Maor Ben

Reputation: 347

Another approach with complexity of n(Math.floor(str.length / 3)) instead of n(str.length)

function formatNumber(num) {
  let str = String(num)
  
  if (num < 1000) return str

  let result = ''

  for (let i = Math.floor(str.length / 3); i >= 0; i--) {
    let start = str.length - 3 * (i + 1)
    let end = start + 3

    result +=
      (end ? str.slice(Math.max(0, start), end) : '') + (end && i ? ',' : '')
  }

  return result
}

Upvotes: 0

Pikamander2
Pikamander2

Reputation: 8319

Here's a simple reusable function that returns a string with the specified number of decimal places and lets you toggle the inclusion of a comma.

function format_number(number, num_decimals, include_comma)
{
    return number.toLocaleString('en-US', {useGrouping: include_comma, minimumFractionDigits: num_decimals, maximumFractionDigits: num_decimals});
}

Usage examples:

format_number(1234.56789, 2, true); // Returns '1,234.57'
format_number(9001.42, 0, false); // Returns '9001'

If you need to customize the string further, you can find the list of formatting options here.

Upvotes: 8

Kerry Jones
Kerry Jones

Reputation: 21848

I suggest using phpjs.org 's number_format()

function number_format(number, decimals, dec_point, thousands_sep) {
    var n = !isFinite(+number) ? 0 : +number, 
        prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
        sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
        dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
        toFixedFix = function (n, prec) {
            // Fix for IE parseFloat(0.55).toFixed(0) = 0;
            var k = Math.pow(10, prec);
            return Math.round(n * k) / k;
        },
        s = (prec ? toFixedFix(n, prec) : Math.round(n)).toString().split('.');
    if (s[0].length > 3) {
        s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
    }
    if ((s[1] || '').length < prec) {
        s[1] = s[1] || '';
        s[1] += new Array(prec - s[1].length + 1).join('0');
    }
    return s.join(dec);
}

UPDATE 02/13/14

People have been reporting this doesn't work as expected, so I did a JS Fiddle that includes automated tests.

Update 26/11/2017

Here's that fiddle as a Stack Snippet with slightly modified output:

function number_format(number, decimals, dec_point, thousands_sep) {
    var n = !isFinite(+number) ? 0 : +number, 
        prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
        sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
        dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
        toFixedFix = function (n, prec) {
            // Fix for IE parseFloat(0.55).toFixed(0) = 0;
            var k = Math.pow(10, prec);
            return Math.round(n * k) / k;
        },
        s = (prec ? toFixedFix(n, prec) : Math.round(n)).toString().split('.');
    if (s[0].length > 3) {
        s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
    }
    if ((s[1] || '').length < prec) {
        s[1] = s[1] || '';
        s[1] += new Array(prec - s[1].length + 1).join('0');
    }
    return s.join(dec);
}

var exampleNumber = 1;
function test(expected, number, decimals, dec_point, thousands_sep)
{
    var actual = number_format(number, decimals, dec_point, thousands_sep);
    console.log(
        'Test case ' + exampleNumber + ': ' +
        '(decimals: ' + (typeof decimals === 'undefined' ? '(default)' : decimals) +
        ', dec_point: "' + (typeof dec_point === 'undefined' ? '(default)' : dec_point) + '"' +
        ', thousands_sep: "' + (typeof thousands_sep === 'undefined' ? '(default)' : thousands_sep) + '")'
    );
    console.log('  => ' + (actual === expected ? 'Passed' : 'FAILED') + ', got "' + actual + '", expected "' + expected + '".');
    exampleNumber++;
}

test('1,235',    1234.56);
test('1 234,56', 1234.56, 2, ',', ' ');
test('1234.57',  1234.5678, 2, '.', '');
test('67,00',    67, 2, ',', '.');
test('1,000',    1000);
test('67.31',    67.311, 2);
test('1,000.6',  1000.55, 1);
test('67.000,00000', 67000, 5, ',', '.');
test('1',        0.9, 0);
test('1.20',     '1.20', 2);
test('1.2000',   '1.20', 4);
test('1.200',    '1.2000', 3);
.as-console-wrapper {
  max-height: 100% !important;
}

Upvotes: 123

areg_noid
areg_noid

Reputation: 91

Related to @elias-zamaria and @t.j.crowder

A negative lookbehind for Safari browser is can't use <. So, it would be (?!\.\d*)

function numberWithCommas(n) {
  return n.toString().replace(/\B(?!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

It works for Safari and Firefox

Upvotes: 4

Elias Zamaria
Elias Zamaria

Reputation: 101153

I used the idea from Kerry's answer, but simplified it since I was just looking for something simple for my specific purpose. Here is what I have:

function numberWithCommas(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

function numberWithCommas(x) {
    return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

function test(x, expect) {
    const result = numberWithCommas(x);
    const pass = result === expect;
    console.log(`${pass ? "✓" : "ERROR ====>"} ${x} => ${result}`);
    return pass;
}

let failures = 0;
failures += !test(0,        "0");
failures += !test(100,      "100");
failures += !test(1000,     "1,000");
failures += !test(10000,    "10,000");
failures += !test(100000,   "100,000");
failures += !test(1000000,  "1,000,000");
failures += !test(10000000, "10,000,000");
if (failures) {
    console.log(`${failures} test(s) failed`);
} else {
    console.log("All tests passed");
}
.as-console-wrapper {
    max-height: 100% !important;
}


The regex uses 2 lookahead assertions:

  • a positive one to look for any point in the string that has a multiple of 3 digits in a row after it,
  • a negative assertion to make sure that point only has exactly a multiple of 3 digits. The replacement expression puts a comma there.

For example, if you pass it 123456789.01, the positive assertion will match every spot to the left of the 7 (since 789 is a multiple of 3 digits, 678 is a multiple of 3 digits, 567, etc.). The negative assertion checks that the multiple of 3 digits does not have any digits after it. 789 has a period after it so it is exactly a multiple of 3 digits, so a comma goes there. 678 is a multiple of 3 digits but it has a 9 after it, so those 3 digits are part of a group of 4, and a comma does not go there. Similarly for 567. 456789 is 6 digits, which is a multiple of 3, so a comma goes before that. 345678 is a multiple of 3, but it has a 9 after it, so no comma goes there. And so on. The \B keeps the regex from putting a comma at the beginning of the string.

@neu-rah mentioned that this function adds commas in undesirable places if there are more than 3 digits after the decimal point. If this is a problem, you can use this function:

function numberWithCommas(x) {
    var parts = x.toString().split(".");
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return parts.join(".");
}

function numberWithCommas(x) {
    var parts = x.toString().split(".");
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return parts.join(".");
}

function test(x, expect) {
    const result = numberWithCommas(x);
    const pass = result === expect;
    console.log(`${pass ? "✓" : "ERROR ====>"} ${x} => ${result}`);
    return pass;
}

let failures = 0;
failures += !test(0              , "0");
failures += !test(0.123456       , "0.123456");
failures += !test(100            , "100");
failures += !test(100.123456     , "100.123456");
failures += !test(1000           , "1,000");
failures += !test(1000.123456    , "1,000.123456");
failures += !test(10000          , "10,000");
failures += !test(10000.123456   , "10,000.123456");
failures += !test(100000         , "100,000");
failures += !test(100000.123456  , "100,000.123456");
failures += !test(1000000        , "1,000,000");
failures += !test(1000000.123456 , "1,000,000.123456");
failures += !test(10000000       , "10,000,000");
failures += !test(10000000.123456, "10,000,000.123456");
if (failures) {
    console.log(`${failures} test(s) failed`);
} else {
    console.log("All tests passed");
}
.as-console-wrapper {
    max-height: 100% !important;
}

@t.j.crowder pointed out that now that JavaScript has lookbehind (support info), it can be solved in the regular expression itself:

function numberWithCommas(x) {
    return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

function numberWithCommas(x) {
    return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

function test(x, expect) {
    const result = numberWithCommas(x);
    const pass = result === expect;
    console.log(`${pass ? "✓" : "ERROR ====>"} ${x} => ${result}`);
    return pass;
}

let failures = 0;
failures += !test(0,               "0");
failures += !test(0.123456,        "0.123456");
failures += !test(100,             "100");
failures += !test(100.123456,      "100.123456");
failures += !test(1000,            "1,000");
failures += !test(1000.123456,     "1,000.123456");
failures += !test(10000,           "10,000");
failures += !test(10000.123456,    "10,000.123456");
failures += !test(100000,          "100,000");
failures += !test(100000.123456,   "100,000.123456");
failures += !test(1000000,         "1,000,000");
failures += !test(1000000.123456,  "1,000,000.123456");
failures += !test(10000000,        "10,000,000");
failures += !test(10000000.123456, "10,000,000.123456");
if (failures) {
    console.log(`${failures} test(s) failed`);
} else {
    console.log("All tests passed");
}
.as-console-wrapper {
    max-height: 100% !important;
}

(?<!\.\d*) is a negative lookbehind that says the match can't be preceded by a . followed by zero or more digits. The negative lookbehind is faster than the split and join solution (comparison), at least in V8.

Upvotes: 3941

uKolka
uKolka

Reputation: 38750

I'm surprised nobody mentioned Number.prototype.toLocaleString. It's implemented in JavaScript 1.5 (which was introduced in 1999) so it's basically supported across all major browsers.

var n = 34523453.345;
console.log(n.toLocaleString());    // "34,523,453.345"

It also works in Node.js as of v0.12 via inclusion of Intl

If you want something different, Numeral.js might be interesting.

Upvotes: 3249

MMMahdy-PAPION
MMMahdy-PAPION

Reputation: 1111

Universal, fast, accurate, simple function

  • Using RegEx (Fast & Accurate)
  • Support Numbers(Float/Integer)/String/Multiple numbers in a string
  • Smart well (Not grouping decimals - Compatible with different types of grouping)
  • Support all browsers specially 'Safari' & 'IE' & many older browsers
  • [Optional] Respecting non-English (Persian/Arabic) digits (+ Pre-fix)
TL;DR - Full version function (minified):

// num: Number/s (String/Number),
// sep: Thousands separator (String) - Default: ','
// dec: Decimal separator (String) - Default: '.' (Just one char)
// u: Universal support for languages characters (String - RegEx character set / class) - Example: '[\\d\\u0660-\\u0669\\u06f0-\\u06f9]' (English/Persian/Arabic), Default: '\\d' (English)

function formatNums(num,sep,dec,u){sep=sep||',';u=u||'\\d';if(typeof num!='string'){num=String(num);if(dec&&dec!='.')num=num.replace('.',dec);}return num.replace(RegExp('\\'+(dec||'.')+u+'+|'+u+'(?=(?:'+u+'{3})+(?!'+u+'))','g'),function(a){return a.length==1?a+sep:a})}

text='100000000 English or Persian/Arabic ۱۲۳۴۵۶۷۸۹/٠١٢٣٤٥٦٧٨٩ this is 123123123.123123123 with this -123123 and these 10 100 1000 123123/123123 (2000000) .33333 100.00 or any like 500000Kg';

console.log(formatNums(10000000.0012));
console.log(formatNums(10000000.0012,'.',',')); // German
console.log(formatNums(text,',','.','[\\d\\u0660-\\u0669\\u06f0-\\u06f9]')); // Respect Persian/Arabic digits
<input oninput="document.getElementById('result').textContent=formatNums(this.value)" placeholder="Type a number here">
<div id="result"></div>

Why NOT satisfied with other answers?

  • Number.prototype.toLocaleString() / Intl.NumberFormat (Right answer)
    • If no well arguments, we can't expect same result. Also with arguments options we still can't be sure what can be the result because it will use local settings and possible client modifications effect on it or the browser/device not support it.
    • >~2016 browsers support and still in 2021 some reports that in some cases like Safari or IE/Edge do not return as expected.
    • toLocaleString() Work with numbers, Intl.NumberFormat Work with String/Numbers; Strings will be/have to be parsed and also rounded if necessary, so:
      • If we already have a localized string with non-English digits we have to replace numbers with the English one, then parse it, then use it again with the local options. (If it return what we expect)
      • Generally while parsing we cant expect not missing decimal zeros or details in big numbers or respecting other languages numeral characters
    • Decimal / Thousand separator characters can not be customized more than language options, except with post-fixings with replace() + RegEx again. (For example in Persian usually we don't use the suggested Arabic comma and also sometime we use Fraction/Division slash as decimal separator)
    • Slow performance in loops
  • Not so good RegEx ways (Fastest & One-liner ways)
    • /\B(?=(\d{3})+\b)/ it will group decimals too. // 123,123.123,123 !!!
    • /(?<!\.\d+)\B(?=(\d{3})+\b)/ used look-behind that not supported well yet. Please check:
      https://caniuse.com/js-regexp-lookbehind
      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#browser_compatibility
      Note: Generally lookbehind can be against of original RegEx structure (because of how the analyzer should work like do not buffer the raw behind as a parser) and actually it can make the performance seriously low (In this case ~30%). I think it pushed inside over the time by requests.
    • /\B(?=(?=\d*\.)(\d{3})+(?!\d))/ just work with float numbers and ignore integers.
    • .replace(/(?:[^.\d]|^)\d+/g,function(a){return a.replace(/\B(?=(?:\d{3})+\b)/g,',');}) (My old idea) Using 2 RegEx. First one find the integer parts, second one put separator. Why 2 functions, when it can be mixed?
    • /(\..*)$|(\d)(?=(\d{3})+(?!\d))/g (Good idea by @djulien - i voted up) but when the RegEx is global, (\..*)$ it can make a mistake even with a space in end.
      Also using capturing groups (Example: (\d)) will make the performance low so if it possible, use non-capturing groups (Example: (?:\d)) or if an statement already exist in our function let's mix it.
      In this case, not using capturing groups improve performance about ~20% and in case of /\B(?=(\d{3})+\b)/g vs /\B(?=(?:\d{3})+\b)/g, the second one is ~8% faster.
      About regex performances: https://jsben.ch/HUmDA Note: Sure different methods, browsers, hardware, system status, cases and even changes on ECMAScript will effect on result of checking performance. But some changes logically should effect result and i used this one just as visual example.
  • Using library's like Numeral.js so much not necessary functions for a simple task.
  • Heavy code / Not accurate functions that used .split('.') or .toFixed() or Math.floor() ...

Final result:

There is no best of all and it should be chosen based on the need. My priority of sorting;

  1. Compatibility
  2. Capability
  3. Universality
  4. Ease of use
  5. Performance

toLocaleString() (Compatibility - Universality) [Native function]

  • If you have to change digits and grouping from English to another language
  • If you are not sure about your client language
  • If you don't need to have exact expected result
  • If you don't care about older version of Safari
// 1000000.2301
parseFloat(num) // (Pre-fix) If the input is string
    .toLocaleString('en-US', {
        useGrouping: true // (Default is true, here is just for show)
    });
// 1,000,000.23

Read more: https://www.w3schools.com/jsref/jsref_tolocalestring_number.asp

Intl.NumberFormat() (Capability - Universality - Compatibility) [Native function]

Almost same as toLocaleString() +

  • Great capability of supporting currency, units, etc... any language (Modern browsers)
// 1000000.2301
new Intl.NumberFormat('en-US', { // It can be 'fa-IR' : Farsi - Iran
    numberingSystem: 'arab'
}).format(num)
// ١٬٠٠٠٬٠٠٠٫٢٣

Read more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat

With these much options of the native functions, we still can not expect:

  • Exact result (+ Not parsing the input / Not rounding / Not converting big numbers)
  • Accepting other languages digits as input
  • Customizing separators
  • Trusting browsers support
  • Performance

So you maybe need a function like any of these:

formatNums() (Compatibility - Ease of use)

Full version (Capability) (Not faster than toLocaleString) - Explain:

function formatNums(num, sep, dec, u) {
    // Setting defaults
    sep = sep || ','; // Seperator
    u = u || '\\d'; // Universal character set \d: 0-9 (English)
    // Mixing of Handeling numbers when the decimal character should be changed + Being sure the input is string
    if (typeof num != 'string') {
        num = String(num);
        if (dec && dec != '.') num = num.replace('.', dec); // Replacing sure decimal character with the custom
    }
    //
    return num.replace(RegExp('\\' + (dec || '.') + u + '+|' + u + '(?=(?:' + u + '{3})+(?!' + u + '))', 'g'),
        // The RegEx will be like /\.\d+|\d(?=(?:\d{3})+(?!\d))/g if not be customized 
        // RegEx explain:
        // 1) \.\d+  :  First try to get any part that started with a dot and followed by any much of English digits, one or more (For ignoring it later)
        // 2) |  :  Or
        // 3) \d  :  Get any 1 char digit
        // 3.1) (?=...)  :  That the next of that should be
        // 3.2) (?:\d{3})  :  3 length digits
        // 3.2.1) +  :  One or more of the group
        // 3.3) (?!\d)  :  ...till any place that there is no digits
        function(a) { // Any match can be the decimal part or the integer part so lets check it
            return a.length == 1 ? a + sep : a // If the match is one character, it is from the grouping part as item (3) in Regex explain so add the seperator next of it, if not, ignore it and return it back.
        })
}

function formatNums(num,sep,dec,u) {
    sep=sep||',';
    u=u||'\\d';
    if(typeof num!='string') {
        num=String(num);
        if( dec && dec!='.') num=num.replace('.',dec);
    }
    return num.replace(RegExp('\\'+(dec||'.')+u+'+|'+u+'(?=(?:'+u+'{3})+(?!'+u+'))','g'),function(a) {return a.length==1 ? a+sep : a})
}
console.log(formatNums(1000000.2301));
console.log(formatNums(100.2301));
console.log(formatNums(-2000.2301));
console.log(formatNums(123123123,' , '));
console.log(formatNums('0000.0000'));
console.log(formatNums('5000000.00'));
console.log(formatNums('5000000,00',' ',','));
console.log(formatNums(5000000.1234,' ',','));
console.log(formatNums('۱۲۳۴۵۶۷۸۹/۹۰۰۰',',','/','[\\d\\u0660-\\u0669\\u06f0-\\u06f9]'));

Play with the examples here: https://jsfiddle.net/PAPIONbit/198xL3te/

Light version (Performance) (~30% faster than toLocaleString)

function formatNums(num,sep) {
    sep=sep||',';
    return String(num).replace(/\.\d+|\d(?=(?:\d{3})+(?!\d))/g,
        function(a) {
            return a.length==1?a+sep:a
        }
    );
}
console.log(formatNums(1000000.2301));
console.log(formatNums(100.2301));
console.log(formatNums(-2000.2301));
console.log(formatNums(123123123,' '));

Check the RegEx (Without the necessary function) : https://regexr.com/66ott

(num+'').replace(/\B(?=(?:\d{3})+\b)/g,','); (Performance - Compatibility)

Best choose if The input is Specified / Predefined. (Like usual prices that sure will not have more than 3 decimals) (~65% faster than toLocaleString)

num=1000000;
str='123123.100';
console.log((num+'').replace(/\B(?=(?:\d{3})+\b)/g,','));
console.log(str.replace(/\B(?=(?:\d{3})+\b)/g,','));

+

For Persian/Arabic local clients:

If your client going to use Persian/Arabic numbers for input as what is usual in Iran, I think the best way is instead of keeping the original characters, convert them to English before you deal with, to you can calculate it.

// ۱۲۳۴۵۶۷۸۹۰
function toEnNum(n) { // Replacing Persian/Arabic numbers character with English
    n.replace(/[\u0660-\u0669\u06f0-\u06f9]/g, // RegEx unicode range Persian/Arabic numbers char
        function(c) {
            return c.charCodeAt(0) & 0xf; // Replace the char with real number by getting the binary index and breaking to lowest using 15
        }
    );
}
// 1234567890

And for still showing them as original looking there is 2 ways:


My Old-school function on this post: (~15% Faster than toLocalString)

// 10000000.0012
function formatNums(n, s) {
    return s = s || ",", String(n).
    replace(/(?:^|[^.\d])\d+/g, // First this RegEx take just integer parts
        function(n) {
            return n.replace(/\B(?=(?:\d{3})+\b)/g, s);
        })
}
// 10,000,000.0012

Upvotes: 13

Maor Ben
Maor Ben

Reputation: 347

let formatNumber = (number) => {
    let str = String(number)

    return str.split('').reduce(
        (a, b, i) => a + (i && !((str.length - i) % 3) ? ',' : '') + b,
        ''
    )
}

Upvotes: 1

Robo Robok
Robo Robok

Reputation: 22785

If you're looking for a short and sweet solution:

const number = 12345678.99;

const numberString = String(number).replace(
    /^\d+/,
    number => [...number].map(
        (digit, index, digits) => (
            !index || (digits.length - index) % 3 ? '' : ','
        ) + digit
    ).join('')
);

// numberString: 12,345,678.99

Upvotes: 5

Abbas
Abbas

Reputation: 1255

You can create a function on the Number prototype

Number.prototype.format = function (s, d) {
  return (
    this.toString()
      .split(".")
      .map((n, i) =>
        i
          ? n
          : n
              .split("")
              .map((n, i) => (i % 3 || !i ? n : s + n))
              .join("")
      )
      .join(d)
  );
};

console.log((8800.00).format(',', '.'))
// 8,880.00

// French notation
console.log((8800.00).format(' ', ','))
// 8 880,00

Upvotes: 0

Javier Elices
Javier Elices

Reputation: 2154

I am quite impressed by the number of answers this question has got. I like the answer by uKolka:

n.toLocaleString()

But unfortunately, in some locales like Spanish, it does not work (IMHO) as expected for numbers below 10,000:

Number(1000).toLocaleString('ES-es')

Gives 1000 and not 1.000.

See toLocaleString not working on numbers less than 10000 in all browsers to know why.

So I had to use the answer by Elias Zamaria choosing the right thousands separator character:

n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, Number(10000).toLocaleString().substring(2, 3))

This one works well as a one-liner for both locales that use , or . as the thousands separator and starts working from 1,000 in all cases.

Number(1000).toString().replace(/\B(?=(\d{3})+(?!\d))/g, Number(10000).toLocaleString().substring(2, 3))

Gives 1.000 with a Spanish locale context.

Should you want to have absolute control over the way a number is formatted, you may also try the following:

let number   = 1234.567
let decimals = 2
let decpoint = '.' // Or Number(0.1).toLocaleString().substring(1, 2)
let thousand = ',' // Or Number(10000).toLocaleString().substring(2, 3)

let n = Math.abs(number).toFixed(decimals).split('.')
n[0] = n[0].split('').reverse().map((c, i, a) =>
  i > 0 && i < a.length && i % 3 == 0 ? c + thousand : c
).reverse().join('')
let final = (Math.sign(number) < 0 ? '-' : '') + n.join(decpoint)

console.log(final)

Gives 1,234.57.

This one does not need a regular expression. It works by adjusting the number to the desired amount of decimals with toFixed first, then dividing it around the decimal point . if there is one. The left side is then turned into an array of digits which is reversed. Then a thousands separator is added every three digits from the start and the result reversed again. The final result is the union of the two parts. The sign of the input number is removed with Math.abs first and then put back if necessary.

It is not a one-liner but not much longer and easily turned into a function. Variables have been added for clarity, but those may be substituted by their desired values if known in advance. You may use the expressions that use toLocaleString as a way to find out the right characters for the decimal point and the thousands separator for the current locale (bear in mind that those require a more modern Javascript.)

Upvotes: 44

djulien
djulien

Reputation: 141

For anyone who likes 1-liners and a single regex, but doesn't want to use split(), here is an enhanced version of the regex from other answers that handles (ignores) decimal places:

    var formatted = (x+'').replace(/(\..*)$|(\d)(?=(\d{3})+(?!\d))/g, (digit, fract) => fract || digit + ',');

The regex first matches a substring starting with a literal "." and replaces it with itself ("fract"), and then matches any digit followed by multiples of 3 digits and puts "," after it.

For example, x = 12345678.12345678 will give formatted = '12,345,678.12345678'.

Upvotes: 7

Felipe Buccioni
Felipe Buccioni

Reputation: 19688

An alternative way, supporting decimals, different separators and negatives.

var number_format = function(number, decimal_pos, decimal_sep, thousand_sep) {
    var ts      = ( thousand_sep == null ? ',' : thousand_sep )
        , ds    = ( decimal_sep  == null ? '.' : decimal_sep )
        , dp    = ( decimal_pos  == null ? 2   : decimal_pos )

        , n     = Math.floor(Math.abs(number)).toString()

        , i     = n.length % 3 
        , f     = ((number < 0) ? '-' : '') + n.substr(0, i)
    ;

    for(;i<n.length;i+=3) {
        if(i!=0) f+=ts;
        f+=n.substr(i,3);
    }

    if(dp > 0) 
        f += ds + parseFloat(number).toFixed(dp).split('.')[1]

    return f;
}

Some corrections by @Jignesh Sanghani, don't forget to upvote his comment.

Upvotes: 6

Lonnie Best
Lonnie Best

Reputation: 11444

My answer is the only answer that completely replaces jQuery with a much more sensible alternative:

function $(dollarAmount)
{
    const locale = 'en-US';
    const options = { style: 'currency', currency: 'USD' };
    return Intl.NumberFormat(locale, options).format(dollarAmount);
}

This solution not only adds commas, but it also rounds to the nearest penny in the event that you input an amount like $(1000.9999) you'll get $1,001.00. Additionally, the value you input can safely be a number or a string; it doesn't matter.

If you're dealing with money, but don't want a leading dollar sign shown on the amount, you can also add this function, which uses the previous function but removes the $:

function no$(dollarAmount)
{
    return $(dollarAmount).replace('$','');
}

If you're not dealing with money, and have varying decimal formatting requirements, here's a more versatile function:

function addCommas(number, minDecimalPlaces = 0, maxDecimalPlaces = Math.max(3,minDecimalPlaces))
{
    const options = {};
    options.maximumFractionDigits = maxDecimalPlaces;
    options.minimumFractionDigits = minDecimalPlaces;
    return Intl.NumberFormat('en-US',options).format(number);
}

Oh, and by the way, the fact that this code does not work in some old version of Internet Explorer is completely intentional. I try to break IE anytime that I can catch it not supporting modern standards.

Please remember that excessive praise, in the comment section, is considered off-topic. Instead, just shower me with up-votes.

Upvotes: 9

Oliver
Oliver

Reputation: 581

You can also use the Intl.NumberFormat constructor. Here is how you can do it.

 resultNumber = new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(yourNumber); 

Upvotes: 12

My true regular-expressions-only solution for those love one-liners

You see those enthusiastic players above? Maybe you can golf out of it. Here’s my stroke.

n => `${n}`.replace(/(?<!\.\d+)\B(?=(\d{3})+\b)/g, " ").replace(/(?<=\.(\d{3})+)\B/g, " ")

Uses a THIN SPACE (U+2009) for a thousands separator, as the International System of Units said to do in the eighth edition(2006) of their publication “SI Brochure: The International System of Units (SI) (See §5.3.4.). The ninth edition(2019) suggests to use a space for it (See §5.4.4.). You can use whatever you want, including a comma.


See.

const integer_part_only = n => `${n}`.replace(/(?<!\.\d+)\B(?=(\d{3})+\b)/g, " I ");
const fractional_part_only = n => `${n}`.replace(/(?<=\.(\d{3})+)\B/g, " F ");
const both = n => fractional_part_only(integer_part_only(n));

function demo(number) { // I’m using Chrome 74.
	console.log(`${number}
		→ "${integer_part_only(number)}" (integer part only)
		→ "${fractional_part_only(number)}" (fractional part only)
		→ "${both(number)}" (both)
	`);
}
demo(Math.random() * 10e5);
demo(123456789.01234567);
demo(123456789);
demo(0.0123456789);


How does it work?

For an integer part

.replace(/(?<!\.\d+)\B(?=(\d{3})+\b)/g, " I ")
  • .replace(……, " I ") Put “ I ”
    • /……/g at each of
      • \B the in-between of two adjacent digits
        • (?=……)POSITIVE LOOKAHEAD whose right part is
          • (\d{3})+ one or more three-digit chunks
          • \b followed by a non-digit, such as, a period, the ending of the string, et cetera,
        • (?<!……)NEGATIVE LOOKBEHIND excluding ones whose left part
          • \.\d+ is a dot followed by digits (“has a decimal separator”).

For a decimal part

.replace(/(?<=\.(\d{3})+)\B/g, " F ")
  • .replace(……, " F ") Put “ F ”
    • /……/g at each of
      • \B the in-between of two adjacent digits
        • (?<=……)POSITIVE LOOKBEHIND whose left part is
          • \. a decimal separator
          • (\d{3})+ followed by one or more three-digit chunks.

Character classes and boundaries

\d

Matches any digit (Arabic numeral). Equivalent to [0-9].

For example,

  • /\d/ or /[0-9]/ matches 2 in B2 is the suite number.

\b

Matches a word boundary. This is the position where a word character is not followed or preceded by another word-character, such as between a letter and a space. Note that a matched word boundary is not included in the match. In other words, the length of a matched word boundary is zero.

Examples:

  • /\bm/ matches the m in moon ;
  • /oo\b/ does not match the oo in moon, because oo is followed by n which is a word character;
  • /oon\b/ matches the oon in moon, because oon is the end of the string, thus not followed by a word character;
  • /\w\b\w/ will never match anything, because a word character can never be followed by both a non-word and a word character.

\B

Matches a non-word boundary. This is a position where the previous and next character are of the same type: either both must be words, or both must be non-words. Such as between two letters or between two spaces. The beginning and end of a string are considered non-words. Same as the matched word boundary, the matched non-word boundary is also not included in the match.

For example,

  • /\Bon/ matches on in at noon;
  • /ye\B/ matches ye in possibly yesterday.

Browser compatibility

Upvotes: 4

Igor Bykov
Igor Bykov

Reputation: 2822

Just for future Googlers (or not necessarily 'Googlers'):

All of solutions mentioned above are wonderful, however, RegExp might be awfully bad thing to use in a situation like that.

So, yes, you might use some of the options proposed or even write something primitive yet useful like:

const strToNum = str => {

   //Find 1-3 digits followed by exactly 3 digits & a comma or end of string
   let regx = /(\d{1,3})(\d{3}(?:,|$))/;
   let currStr;

   do {
       currStr = (currStr || str.split(`.`)[0])
           .replace( regx, `$1,$2`)
   } while (currStr.match(regx)) //Stop when there's no match & null's returned

   return ( str.split(`.`)[1] ) ?
           currStr.concat(`.`, str.split(`.`)[1]) :
           currStr;

};

strToNum(`123`) // => 123
strToNum(`123456`) // => 123,456
strToNum(`-1234567.0987`) // => -1,234,567.0987

The regexp that's used here is fairly simple and the loop will go precisely the number of times it takes to get the job done.

And you might optimize it far better, "DRYify" code & so on.

Yet,

(-1234567.0987).toLocaleString();

(in most situations) would be a far better choice.

The point is not in the speed of execution or in cross-browser compatibility.

In situations when you'd like to show the resulting number to user, .toLocaleString() method gives you superpower to speak the same language with the user of your website or app (whatever her/his language is).

This method according to ECMAScript documentation was introduced in 1999, and I believe that the reason for that was the hope that the Internet at some point will connect people all around the world, so, some "internalization" tools were needed.

Today the Internet does connect all of us, so, it is important to remember that the world is a way more complex that we might imagine & that (/almost) all of us are here, in the Internet.

Obviously, considering the diversity of people, it is impossible to guarantee perfect UX for everybody because we speak different languages, value different things, etc. And exactly because of this, it is even more important to try to localize things as much as it's possible.

So, considering that there're some particular standards for representation of date, time, numbers, etc. & that we have a tool to display those things in the format preferred by the final user, isn't that rare and almost irresponsible not to use that tool (especially in situations when we want to display this data to the user)?

For me, using RegExp instead of .toLocaleString() in situation like that sounds a little bit like creating a clock app with JavaScript & hard-coding it in such a way so it'll display Prague time only (which would be quite useless for people who don't live in Prague) even though the default behaviour of

new Date();

is to return the data according to final user's clock.

Upvotes: 7

Bathri Nathan
Bathri Nathan

Reputation: 1267

Use This code to handle currency format for india. Country code can be changed to handle other country currency.

let amount =350256.95
var formatter = new Intl.NumberFormat('en-IN', {
  minimumFractionDigits: 2,
});

// Use it.

formatter.format(amount);

output:

3,50,256.95

Upvotes: 14

Kiry Meas
Kiry Meas

Reputation: 1242

var formatNumber = function (number) {
  var splitNum;
  number = Math.abs(number);
  number = number.toFixed(2);
  splitNum = number.split('.');
  splitNum[0] = splitNum[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  return splitNum.join(".");
}

EDIT: The function only work with positive number. for exmaple:

var number = -123123231232;
formatNumber(number)

Output: "123,123,231,232"

But to answer the question above toLocaleString() method just solves the problem.

var number = 123123231232;
    number.toLocaleString()

Output: "123,123,231,232"

Cheer!

Upvotes: 11

Noah Freitas
Noah Freitas

Reputation: 17430

Here's a simple function that inserts commas for thousand separators. It uses array functions rather than a RegEx.

/**
 * Format a number as a string with commas separating the thousands.
 * @param num - The number to be formatted (e.g. 10000)
 * @return A string representing the formatted number (e.g. "10,000")
 */
var formatNumber = function(num) {
    var array = num.toString().split('');
    var index = -3;
    while (array.length + index > 0) {
        array.splice(index, 0, ',');
        // Decrement by 4 since we just added another unit to the array.
        index -= 4;
    }
    return array.join('');
};

CodeSandbox link with examples: https://codesandbox.io/s/p38k63w0vq

Upvotes: 15

aleclarson
aleclarson

Reputation: 19055

I've found an approach that works in every situation. CodeSandbox example

function commas(n) {
  if (n < 1000) {
    return n + ''
  } else {
    // Convert to string.
    n += ''

    // Skip scientific notation.
    if (n.indexOf('e') !== -1) {
      return n
    }

    // Support fractions.
    let i = n.indexOf('.')
    let f = i == -1 ? '' : n.slice(i)
    if (f) n = n.slice(0, i)

    // Add commas.
    i = n.length
    n = n.split('')
    while (i > 3) n.splice((i -= 3), 0, ',')
    return n.join('') + f
  }
}

This is like Noah Freitas' answer, but with support for fractions and scientific notation.

I think toLocaleString is the best choice, if performance is not a concern.

edit: Here's a CodeSandbox with some examples: https://codesandbox.io/s/zmvxjpj6x

Upvotes: 1

T&#237;nh Ng&#244; Quang
T&#237;nh Ng&#244; Quang

Reputation: 4652

Using Regular expression

function toCommas(value) {
    return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
console.log(toCommas(123456789)); // 123,456,789

console.log(toCommas(1234567890)); // 1,234,567,890
console.log(toCommas(1234)); // 1,234

Using toLocaleString()

var number = 123456.789;

// request a currency format
console.log(number.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' }));
// → 123.456,79 €

// the Japanese yen doesn't use a minor unit
console.log(number.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }))
// → ¥123,457

// limit to three significant digits
console.log(number.toLocaleString('en-IN', { maximumSignificantDigits: 3 }));
// → 1,23,000

ref MDN:Number.prototype.toLocaleString()

Using Intl.NumberFormat()

var number = 123456.789;

console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(number));
// expected output: "123.456,79 €"

// the Japanese yen doesn't use a minor unit
console.log(new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(number));
// expected output: "¥123,457"

// limit to three significant digits
console.log(new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(number));

// expected output: "1,23,000"

ref Intl.NumberFormat

DEMO AT HERE

<script type="text/javascript">
  // Using Regular expression
  function toCommas(value) {
    return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  }

  function commas() {
    var num1 = document.myform.number1.value;

    // Using Regular expression
    document.getElementById('result1').value = toCommas(parseInt(num1));
    // Using toLocaleString()

    document.getElementById('result2').value = parseInt(num1).toLocaleString('ja-JP', {
      style: 'currency',
      currency: 'JPY'
    });

    // Using Intl.NumberFormat()
    document.getElementById('result3').value = new Intl.NumberFormat('ja-JP', {
      style: 'currency',
      currency: 'JPY'
    }).format(num1);
  }
</script>
<FORM NAME="myform">
  <INPUT TYPE="text" NAME="number1" VALUE="123456789">
  <br>
  <INPUT TYPE="button" NAME="button" Value="=>" onClick="commas()">
  <br>Using Regular expression
  <br>
  <INPUT TYPE="text" ID="result1" NAME="result1" VALUE="">
  <br>Using toLocaleString()
  <br>
  <INPUT TYPE="text" ID="result2" NAME="result2" VALUE="">
  <br>Using Intl.NumberFormat()
  <br>
  <INPUT TYPE="text" ID="result3" NAME="result3" VALUE="">

</FORM>

Performance

Performance http://jsben.ch/sifRd

Upvotes: 76

Sverrisson
Sverrisson

Reputation: 18167

After not finding a modern and comprehensive solution here, I have written an arrow function (without regex) to solve the formatting problem and it allows the caller to provide number of fraction digits as well as the period and thousand separator for Europe and rest of the world.

Examples:

numberFormatter(1234567890.123456) => 1,234,567,890
numberFormatter(1234567890.123456, 4) => 1,234,567,890.1235
numberFormatter(1234567890.123456, 4, '.', ',') => 1.234.567.890,1235 Europe

Here is the function written in ES6 (modern syntax):

const numberFormatter = (number, fractionDigits = 0, thousandSeperator = ',', fractionSeperator = '.') => {
    if (number!==0 && !number || !Number.isFinite(number)) return number
    const frDigits = Number.isFinite(fractionDigits)? Math.min(Math.max(fractionDigits, 0), 7) : 0
    const num = number.toFixed(frDigits).toString()

    const parts = num.split('.')
    let digits = parts[0].split('').reverse()
    let sign = ''
    if (num < 0) {sign = digits.pop()}
    let final = []
    let pos = 0

    while (digits.length > 1) {
        final.push(digits.shift())
        pos++
        if (pos % 3 === 0) {final.push(thousandSeperator)}
    }
    final.push(digits.shift())
    return `${sign}${final.reverse().join('')}${frDigits > 0 ? fractionSeperator : ''}${frDigits > 0 && parts[1] ? parts[1] : ''}`
}

It has been tested for negative, bad input and NaN cases. If the input is NaN then it simply returns it.

Upvotes: 2

Du-Lacoste
Du-Lacoste

Reputation: 12787

You can either use this procedure to format your currency needing.

var nf = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});
nf.format(123456.789); // ‘$123,456.79’

For more info you can access this link.

https://www.justinmccandless.com/post/formatting-currency-in-javascript/

Upvotes: 23

Dustin Sun
Dustin Sun

Reputation: 5532

Intl.NumberFormat

Native JS function. Supported by IE11, Edge, latest Safari, Chrome, Firefox, Opera, Safari on iOS and Chrome on Android.

var number = 3500;

console.log(new Intl.NumberFormat().format(number));
// → '3,500' if in US English locale

Upvotes: 49

Related Questions