Commafy Numbers

I've never used the few scripts I've seen that add commas to numbers because usually I want to apply the functionality to entire blocks of text. Having to pull out numbers, add commas, then put them back becomes a needlessly complex task without a method which can just do this in one shot. So, here's my attempt at this (if JavaScript regexes supported lookbehind, it could be even shorter):

String.prototype.commafy = function () {
	return this.replace(/(^|[^\w.])(\d{4,})/g, function($0, $1, $2) {
		return $1 + $2.replace(/\d(?=(?:\d\d\d)+(?!\d))/g, "$&,");
	});
}

Number.prototype.commafy = function () {
	return String(this).commafy();
}

Here are a couple examples of how this can be used:

(1000).commafy();
// Output: 1,000

var data = '1\n' +
	'10\n' +
	'100\n' +
	'1000\n' +
	'10000\n' +
	'100000\n' +
	'1000000\n' +
	'12345678901234567890\n' +
	'1000.99\n' +
	'1000.9999\n' +
	'.9999\n' +
	'-1000\n' +
	'$1000\n' +
	'"1000"\n' +
	'1000MHz\n' +
	'Z1000';

data.commafy();
/* Output:
1
10
100
1,000
10,000
100,000
1,000,000
12,345,678,901,234,567,890
1,000.99
1,000.9999
.9999
-1,000
$1,000
"1,000"
1,000MHz
Z1000
*/

Note that it adds commas to numbers followed by non-numeric characters, but avoids adding commas to numbers immediately preceded by a dot (decimal point), letter, or underscore. And as shown, this can be applied to individual numbers or entire blocks of text.

This is a decent example of where regular expressions can help to shorten and simplify code even in places you may not initially think to use them.


Edit: I've included an alternative implementation of the above code in my post Mimicking Lookbehind in JavaScript, which temporarily reverses the string to allow a simplified approach.

12 thoughts on “Commafy Numbers”

  1. Okay dude, you’re my Regex hero now. I had a function that did this in CFJS, but which was much larger. You relegated it to a couple of lines. Beautiful. :o)

    I’ve got a problem though. I always run CFJS through JSLint to make sure that when compressed the library still works. I ran it through JSLint this time and got this response:

    Error:

    Problem at line 76 character 11: Expected an identifier and instead saw ‘/’.

    return /[.\w]/.test($1) ? $0 : $1 + $2.replace(/\d(?=(?:\d\d\d)+(?!\d))/g,…

    Problem at line 76 character 11: Stopping, unable to continue. (10% scanned).

    return /[.\w]/.test($1) ? $0 : $1 + $2.replace(/\d(?=(?:\d\d\d)+(?!\d))/g,…

    you mind helping me fix that? Commafy works great in my uncompressed code, and it again lowers the byte count of the overall library.

    Also, I just tried running your exact code from this post in JSLint and it returns the same error.

  2. That is either a bug or some kind of quirk in JSLint. You can work around it by wrapping /[.\w]/ in parentheses (e.g., (/[.\w]/).test($1)), but you might want to report the issue to JSLint’s author (Douglas Crockford).

  3. Maybe in previous versions. I just tested the code in Safari 3.0.1 beta for Windows XP, and the replacement function works fine. However, I did discover one very specific, very bizarre bug. With the above code, Safari incorrectly outputs numbers which are four digits or longer, start with 0, 1, or 2, and are immediately preceded by $ (yes, the issue is that specific).

    The bug involves the capturing groups of the first regular expression somehow interfering with the data returned by the replacement function when the data contains a dollar sign followed by a number for which there is a corresponding capturing group in the first regex. The data corruption does not directly result from the regex or any other JavaScript code run over the data. I’ll report this issue to Apple, but can anyone confirm if the bug exists in Safari for Mac?

    Here’s reduced code to reproduce the bug:

    var str = ‘$1’;
    alert(str.replace(/(\D)(\d)/g, function($0){return $0;}));

    Values to set for str, and the results:

    – ‘$1’ (as shown) incorrectly returns just $.
    – ‘$2’ incorrectly returns just 2.
    – Anything else correctly returns the entire, original test string.

    The alternative implementation of String.commafy() posted in Mimicking Lookbehind in JavaScript does not invoke this bug, and hence works without issue in Safari.

  4. To be fair, I saw your comment on this blog post shortly after you posted it and had started working on a fix before I got around to filing the bug report myself. I’m not quite as quick as you suggest 😉

  5. Steve,

    Exquisitely short — Thanks

    Have you considered modifying for international support to handle different comma separators, thousand separators and their spacing (in some cases varied)?

Leave a Reply

Your email address will not be published. Required fields are marked *