It seems every JavaScript blogger out there has his or her own post with a solution for formatting numbers to insert commas. So here are my obligatory two cents.
I’ll try to set my post apart slightly by providing a detailed explanation.
The Regular Expression
Since JavaScript is notoriously terrible when it comes to arithmetic and precision, the original value is treated as a string throughout. No division by 1,000 or anything like that to determine the parts to join.
The regular expression consists of two (top-level) captured groups, which represent the two pieces of the original value that will be joined with a comma.
- The first group,
(-?\d+), captures any number of digits, optionally preceded by a negative sign. - The second group,
(\d{3}(\.\d+)?), captures exactly three digits, optionally followed by a decimal point then one or more digits.
The overall pattern is bracketed by ^ and $ (start-of-value and end-of-value matchers), and since all other matching is done inside the groups, that means that all characters in the original value are captured. This helps ensure that the pattern only matches valid inputs (positive and negative integers and floats).
The Replace
The regular expression is used in String’s replace function. The other parameter to replace is a callback that accepts the captured groups from matching the value with the regular expression.
Note that if the expression matches the value, String.replace will return the callback’s return value. Otherwise, it will return the original string. So, calling 'abcd'.commafy() will just return 'abcd' (as opposed to throwing an error or returning garbage like 'a,bcd').
The Callback
The first value in a match (and the first parameter to the callback) is the original string being matched, which we don’t need. That’s the _ parameter. You can forget that it exists. The first captured group (optional negative sign then one or more digits) is a. The second captured group (three digits then the optional decimal part) is b.
The callback is only adding a single comma, though. Large numbers that need multiple commas are handled by recursively commafying the first captured group.
Step Through
Gist 1654123: commafy.stepThrough.1.js
leads to
Gist 1654123: commafy.stepThrough.2.js
which leads to
Gist 1654123: commafy.stepThrough.3.js
which leads to
Gist 1654123: commafy.stepThrough.4.js
which finally returns
Gist 1654123: commafy.stepThrough.5.js
Prototype
commafy is declared on String’s prototype, so once declared, it’s available on all String instances.
And once it’s part of String’s prototype, you can declare it like so on Number’s prototype for convenience:
Inconsistencies
There are a few inconsistencies that could be addressed with more code. But I like less code.
For example, '1e3'.commafy() would return '1e3' (since the input string doesn’t match the regular expression). Whereas, (1e3).commafy() would return '1,000' since 1e3 is valid numeric syntax in JavaScript, and (1e3).toString() would return '1000', which would cause String’s commafy to return '1,000'.
But exponential syntax is rare enough that I didn’t bother addressing it. The implementation above handles positive and negative integer and decimal numbers and that’s enough coverage for me.