Neil
Neil

Reputation: 14313

Regex to separate thousands with comma and keep two decimals

I recently came up with this code while answering another StackOverflow question. Basically, on blur, this code will properly comma separate by thousands and leave the decimal at two digits (like how USD is written [7,745.56]).

I was wondering if there is more concise way of using regex to , separate and cut off excessive decimal places. I recently updated this post with my most recent attempt. Is there a better way of doing this with regex?

Input -> Target Output

7456 -> 7,456
45345 -> 45,345
25.23523534 -> 25.23
3333.239 -> 3,333.23
234.99 -> 234.99
2300.99 -> 2,300.99
23123123123.22 -> 23,123,123,123.22

Current Regex

var result;
var str = []
reg = new RegExp(/(\d*(\d{2}\.)|\d{1,3})/, "gi");
reversed = "9515321312.2323432".split("").reverse().join("")
while (result = reg.exec(reversed)) {
  str.push(result[2] ? result[2] : result[0])
}
console.log(str.join(",").split("").reverse().join("").replace(",.","."))

Upvotes: 13

Views: 17489

Answers (9)

arc
arc

Reputation: 4691

As an alternative to the Regex, you could use the following approach

Number(num.toFixed(2)).toLocaleString('en-US')

or

num.toLocaleString('en-US', {maximumFractionDigits: 2})

You would still have the toFixed(2), but it's quite clean. toFixed(2) though won't floor the number like you want. Same with {maximumFractionDigits: 2} as the second parameter to toLocaleString as well.

var nums = [7456, 45345, 25.23523534, 3333.239, 234.99, 2300.99, 23123123123.22]

for (var num of nums) 
  console.log(num, '->',  Number(num.toFixed(2)).toLocaleString('en-US') )

Flooring the number like you showed is a bit tricky. Doing something like (num * 100 | 0) / 100 does not work. The calculation loses precision (e.g. .99 will become .98 in certain situations). (also |0 wouldn't work with larger numbers but even Math.floor() has the precision problem).

The solution would be to treat the numbers like strings.

function format(num) {
    var num = num.toLocaleString('en-US')
    var end = num.indexOf('.') < 0 ? num.length : num.indexOf('.') + 3
    return num.substring(0, end)
}

var nums = [7456, 45345, 25.23523534, 3333.239, 234.99, 2300.99, 23123123123.22]

for (var num of nums) console.log(num, '->', format(num))

function format(num) {
  var num = num.toLocaleString('en-US')
  var end = num.indexOf('.') < 0 ? num.length : num.indexOf('.') + 3
  return num.substring(0, end)
}

(when changing to another format than 'en-US' pay attention to the . in numbers as some languages use a , as fractal separator)

For Compatibility, according to CanIUse toLocaleString('en-US') is

supported in effectively all browsers (since IE6+, Firefox 2+, Chrome 1+ etc)

Upvotes: 12

guest271314
guest271314

Reputation: 1

You can use Intl.NumberFormat with style set to "decimal" and maximumFractionDigits set to 2 at options object passed at second parameter

const nums = [7456, 45345, 25.23523534, 3333.239, 234.99, 2300.99, 23123123123.22];

const formatOptions = {style:"decimal", maximumFractionDigits:2};
           
const formatter = new Intl.NumberFormat("en-US", formatOptions);

const formatNums = num => formatter.format(num);

let formattedNums = nums.map(formatNums);

console.log(formattedNums);

Upvotes: 1

Jamin
Jamin

Reputation: 1402

Try:

var n = 5812090285.2817481974897;
n = n.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
console.log(n);

Outputs:

5,812,090,285.28

Note: .toFixed(2) returns a string. So in order to simplify this further you must add a way to turn n into a string before executing your regex. For example:

n.toString.replace(/(\d)(?=(\d{3})+\.)/g, '$1,');  //ofc with the additional regex

Although you would think it wouldn't matter in javascript, it apparently does in this situation. So I dont know how much 'less' messy it would be to not use.

Upvotes: 4

Mayank Raj
Mayank Raj

Reputation: 1624

RegEx to rescue again!

My solution has two parts :

.toFixed : Used to limit the decimal limit

/(\d)(?=(\d\d\d)+(?!\d))/g : It makes use of back reference with three digits at a time

Here's everything put together :

// .toFixed((/\./g.test(num)) ? 2 : 0) it tests if the input number has any decimal places, if so limits it to 2 digits and if not, get's rid of it altogether by setting it to 0
num.toFixed((/\./g.test(num)) ? 2 : 0).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"))

You can see it in action here :

var input = [7456, 45345, 25.23523534, 3333.239, 234.99, 2300.99, 23123123123.22]

input.forEach(function(num) {
  $('div')
    .append(
      $('<p>').text(num + ' => ' +
        num.toFixed( (/\./g.test(num))?2:0 ).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"))
    );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div> </div>

NOTE: I've only used jQuery to append the results

Upvotes: 2

Tomas Langkaas
Tomas Langkaas

Reputation: 4731

If you really insist on doing this purely in regex (and truncate instead of round the fractional digits), the only solution I can think of is to use a replacement function as the second argument to .replace():

('' + num).replace(
  /(\d)(?=(?:\d{3})+(?:\.|$))|(\.\d\d?)\d*$/g, 
  function(m, s1, s2){
    return s2 || (s1 + ',');
  }
);

This makes all your test cases pass:

function format(num){
  return ('' + num).replace(
    /(\d)(?=(?:\d{3})+(?:\.|$))|(\.\d\d?)\d*$/g, 
    function(m, s1, s2){
      return s2 || (s1 + ',');
    }
  );
}


test(7456, "7,456");
test(45345, "45,345");
test(25.23523534, "25.23"); //truncated, not rounded
test(3333.239, "3,333.23"); //truncated, not rounded
test(234.99, "234.99");
test(2300.99, "2,300.99");
test(23123123123.22, "23,123,123,123.22");

function test(num, expected){
  var actual = format(num);
  console.log(num + ' -> ' + expected + ' => ' + actual + ': ' + 
    (actual === expected ? 'passed' : 'failed')
   );
}

Upvotes: 9

Pavel Reznikov
Pavel Reznikov

Reputation: 3208

I found a solution based on @Pierre's answer without using of toFixed:

function format(n) {
  n = +n;
  var d = Math.round(n * 100) % 100;
  return (Math.floor(n) + '').replace(/(\d)(?=(\d{3})+$)/g, '$1,') + (d > 9 ? '.' + d : d > 0 ? '.0' + d : '');
}

console.log(format(7456));
console.log(format(7456.0));
console.log(format(7456.1));
console.log(format(7456.01));
console.log(format(7456.001));
console.log(format(45345));
console.log(format(25.23523534));
console.log(format(3333.239));
console.log(format(234.99));
console.log(format(2300.99));
console.log(format(23123123123.22));
console.log(format('23123123123.22'));

Upvotes: 0

bur&#230;quete
bur&#230;quete

Reputation: 14678

I added another layer where regex that drops the unwanted decimals below hundredths on top of your regex comma adding logic;

val.replace(/(\.\d{2})\d*/, "$1").replace(/(\d)(?=(\d{3})+\b)/g, "$1,")

doIt("7456");
doIt("45345");
doIt("25.23523534");
doIt("3333.239");
doIt("234.99");
doIt("2300.99");
doIt("23123123123.22");
doIt("5812090285.2817481974897");

function doIt(val) {
    console.log(val + " -> " + val.replace(/(\.\d{2})\d*/, "$1").replace(/(\d)(?=(\d{3})+\b)/g, "$1,"));
}

If multiple calls of regex replace is OK, this answer should satisfy you, since it is only has regex replace logic and nothing else.

Upvotes: 4

Dhiraj
Dhiraj

Reputation: 1462

You can do like this

(parseFloat(num).toFixed(2)).replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,").replace(".00","")

Here just convert number to formatted number with rounded down to 2 decimal places and then remove the .00 if exist.

This can be one approach you can use.

var format = function (num) {
    
return (parseFloat(num).toFixed(2)).replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,").replace(".00","")
}
$(function () {
    $("#principalAmtOut").blur(function (e) {
        $(this).val(format($(this).val()));
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="principalAmtOut" type="text" />

Upvotes: 1

Martin Parenteau
Martin Parenteau

Reputation: 73721

Here is a way to do it without a regular expression:

value.toLocaleString("en-US", { maximumFractionDigits: 2 })

function formatValue() {
    var source = document.getElementById("source");
    var output = document.getElementById("output");
    var value = parseFloat(source.value);
    output.innerText = value.toLocaleString("en-US", { maximumFractionDigits: 2 });
}
<input id="source" type="text" />
<button onclick="formatValue()">Format</button>
<div id="output"></div>

Upvotes: 2

Related Questions