A recent project I worked on allowed customers to request cloud computing resources. One of the requirements was to display the cost for each resource amount and the totals for one and two years for the resources the customer was requesting. In the United States, numbers are formatted with a period for the decimal mark and a comma as the thousands separator, e.g. 1,234,567.89.
The function below is going to:
- Assume that the input is either an unsigned integer or a float, ex. 1234567 or 1234567.90.
- Insert a "," delimiter every third digit.
- Optionally, add a currency symbol to the returned string.
- On lines 2 - 4 I defined some default values.
- On line 6 I defined a closure called formatNumber. The logic in the closure is going to format the number string.
- Starting at line 28 I am making sure that when the formatCostNumber function is called it does receive a value to format, and that value is numeric; next before formatting said value, I convert it to a string and determine if it contains a period. I only want to format the portion of the string before the period.
- Last, on line 43, before returning the formatted number string, I check if I need to add a currency symbol.
Now, let's take a look at the logic of the formatNumber closure.
- Line 7 - I make sure that the string to format has at least four characters. There is no reason to format something like "123", which will only result in ",123".
- Line 10 - I create a for loop which is going to run for the length of the string (i). This variable will change as the string is modified. The loop will count down to 1. Going in a descending order will allow me to move from right to left as I parse the string. I am counting down to 1 instead of 0 to avoid output like ",123,456". I am also adding two more parameters: hops, which will track the number of turns since last "," insertion; and turns, which will track the actual number of times the loop has run.
- Line 13 - I check if there have been 3 turns since the last insertion and the if the character at the current position of the string is not already a ",". If these conditions are not met then I am going to increment the hops by 1 and loop around.
- Line 14 - If my conditions are met, I am going to insert the thousands' delimiter by concatenating two different strings with the delimiter in between. The first string being the portion of the subject string up to the current position, and the second string being everything after the current position.
- Line 17 - I reset back to zero the count since the last insertion.
- Line 18 - I update the string index position (i) by subtracting actual loop count (turns) from the actual length of the string I just modified. This will prevent results like "12,34,567".
Note: The delimiter insertion loop can be optimized by directly incrementing by 3 after inserting the thousands' delimiter and updating the string length. This will allow for dropping the hops and turns variable, but it will require a check for how many characters a left to the front of the string to avoid ending up with ",123,456".