This project contains nine different language implementations of a simple algorithm named “commaize” that inserts commas into a number. The languages are: bash, c++, java, javascript, perl, php, python, ruby and tcl. My hope is that it will be a useful reference.
Contents
- Introduction
- Downloads
- Download Files
- Installation
- Bash Implementation
- C++ Implementation
- Java Implementation
- Javascript Implementation
- Perl Implementation
- PHP Implementation
- Python Implementation
- Ruby Implementation
- Tcl Implementation
- Tested Platforms
- License
Introduction
I created it because I sometimes have to work on code written in a variety of languages. Having an example like this is really helpful as a reference for languages that I rarely use (like tcl). Note that because these implementations are meant to act as a reference, they are not optimized.
This project contains the algorithm coded in complete standalone programs. Each program prints a similar list of formatted data in 3 columns. The first column is the test number, the second is the language specific format of the input and the third column is the output. That third column must be identical for all programs. In some cases there is a fourth column to remind me of what the row is for. This is what the output looks like for the python implementation (commaize.py):
$ ./commaize.py 1 1 1 2 12 12 3 123 123 4 1234 1,234 5 12345 12,345 6 123456 123,456 7 1234567 1,234,567 8 12345678 12,345,678 9 123456789 123,456,789 10 1.23 1.23 11 12.34 12.34 12 123.45 123.45 13 1234.56 1,234.56 14 12345.67 12,345.67 15 123456.78 123,456.78 16 1234567.89 1,234,567.89 17 12345678.9 12,345,678.9 18 1.0 1 19 -1 -1 20 -12 -12 21 -123 -123 22 -1234 -1,234 23 -12345 -12,345 24 -123456 -123,456 25 -1234567 -1,234,567 26 -12345678 -12,345,678 27 -123456789 -123,456,789 28 -1.23 -1.23 29 -12.34 -12.34 30 -123.45 -123.45 31 -1234.56 -1,234.56 32 -12345.67 -12,345.67 33 -123456.78 -123,456.78 34 -1234567.89 -1,234,567.89 35 -12345678.9 -12,345,678.9 36 -1.0 -1 37 123456789 123,456,789 38 12345678,9 12.345.678,9 # EU style 39 12345.123 12,345.12 # 2 decimal places 40 12345 12,345.00 # 2 decimal places 41 12345.1251 12,345.13 # 2 decimal places 42 1234567.89 1,234,567.9 # 1 decimal place
Note that there are two compiled languages: c++ and java so there is a Makefile that will run the compilations. You will have to edit it to pick up the correct compiler. When you do you will see that I used gcc-4.8.2 installed locally on my Mac as described here: http://joelinoff.com/blog/?p=1003 because I wanted the latest C++-11 features.
The javascript implementation requires browser rendering so it has an associated commaize.html file.
All of the source code is freely available based on the MIT license. See the LICENSE.txt file for details.
↑ Downloads
All of the implementations are available to download. There are three archive formats: tar.bz2, tar.gz and zip.
| File | Size | Checksum | Extract Command |
|---|---|---|---|
| commaize.tar.bz2 | 13K | 60400 |
tar jxf commaize.tar.bz2
|
| commaize.tar.gz | 14K | 5113 |
tar zxf commaize.tar.gz
|
| commaize.zip | 21K | 54782 |
unzip commaize.zip
|
↑ Download Files
The archives contain the following files in a local commaize directory.
| File | Description |
|---|---|
| commaize.html | HTML file that references commaize.js. |
| commaize.cc | The C++ implementation. Uses some features from C++11. |
| commaize.java | The java (1.6) implementation. |
| commaize.js | The javascript implementation. |
| commaize.pl | The perl (5.12) implementation. |
| commaize.php | The php (5.4) implementation. |
| commaize.py | The python (2.7) implementation. |
| commaize.rb | The ruby (0.9.6, 1.8.7) implementation. |
| commaize.sh | The bash implementation. |
| commaize.tcl | The tcl (8.6) implementation. |
| golden.txt | The golden output for testing. |
| LICENSE.txt | The source code license information. |
| Makefile | Builds and tests the implementations. Here are some interesting targets.
You will need to modify this file for your environment. |
| README.txt | Information about the project. |
↑ Installation
The package is available in 3 formats: bzipped tar format, gzipped tar format and zip format. The following example shows how to download, install and run the project using the bzipped tar format.
$ # Installation example.
$ # There are three ways to download the project.
$ wget http://projects.joelinoff.com/commaize/commaize.tar.bz2
$ wget http://projects.joelinoff.com/commaize/commaize.tar.gz
$ wget http://projects.joelinoff.com/commaize/commaize.zip
$ # There are three ways to extract the project.
$ tar jxf commaize.tar.bz2
$ #tar zxf commaize.tar.gz
$ #unzip commaize.zip
$ # Here is the basic recipe but note that you MUST edit the
$ # Makefile to point to the correct local c++ compiler.
$ cd commaize
$ edit Makefile # change the GXX compiler references
$ make
[output snipped]
# ================================================================
# test
# ================================================================
1 test-commaize.class.out passed
2 test-commaize.exe.out passed
3 test-commaize.php.out passed
4 test-commaize.pl.out passed
5 test-commaize.py.out passed
6 test-commaize.rb.out passed
7 test-commaize.sh.out passed
8 test-commaize.tcl.out passed
TOTAL: 8
PASSED: 8
FAILED: 0
↑ Bash Implementation
This is the bash implementation.
#!/bin/bash
#
# Insert thousands separator into a number and, optionally, specify
# the number of decimal places.
#
# Here are some examples that demonstrate how it is used.
#
# x=$(commaize 1234) # 1,234
# x=$(commaize -1234) # -1,234
# x=$(commaize 12345.678) # 12,345.678
# x=$(commaize -12345.678) # -12,345.678
# x=$(commaize 12345.678 2) # 12,345.69
# x=$(commaize 12345.678 1) # 12,345.7
# x=$(commaize 1234 -1 ".") # 1.234
# x=$(commaize "1234567,89" -1 "." ",") # EU format: 1.234.567,89
#
# The arguments are:
#
# $1 == number
# $2 == decimal places (default: -1 -> do nothing)
# $3 == thousands separator (default: ",")
# $4 == decimal point (default: ".")
#
function commaize() {
local num="$1"
local dpl=-1
local sep=","
local dpt="."
if [[ "$2" != "" ]] ; then dpl=$2 ; fi
if [[ "$3" != "" ]] ; then sep="$3" ; fi
if [[ "$4" != "" ]] ; then dpt="$4" ; fi
if (( $dpl >= 0 )) ; then
fmt="%.${dpl}f"
num=$(printf "$fmt" $num)
fi
local neg=""
if [[ "${num:0:1}" == "-" ]] ; then
neg="-"
# Strip off the leading minus sign.
numl=$((${#num} - 1))
num=${num:1:$numl}
fi
local parts=($(echo "$num" | tr "$dpt" "\n"))
local nparts=${#parts[@]}
if (( $nparts > 2 )) ; then
echo "ERROR: invalid string: $num"
exit 1
fi
# Insert the separators.
characteristic=${parts[0]}
result=''
j=$(( ${#characteristic} % 3 ))
for (( i=0; i<${#characteristic}; i++ )); do
k=$(( $i % 3))
if (( $k == $j )) && (( $i > 0 )) ; then
result="$result$sep"
fi
digit=${characteristic:$i:1}
result="$result$digit"
done
# There was a decimal point, append
# the mantissa.
if (( $nparts == 2 )) ; then
mantissa="${parts[1]}"
if (( $dpl < 0 )) ; then
# Strip off trailing zeros if dpl was not not specified.
# Convert 1.0 --> 1
mantissa=$(echo "$mantissa" | sed -e 's/0$//g')
fi
if (( ${#mantissa} > 0 )) ; then
result="$result$dpt$mantissa"
fi
fi
echo "$neg$result"
}
# Main
idx=0
data=(1 12 123 1234 12345 123456 1234567 12345678 123456789
1.23 12.34 123.45 1234.56 12345.67 123456.78 1234567.89 12345678.9
1.0
-1 -12 -123 -1234 -12345 -123456 -1234567 -12345678 -123456789
-1.23 -12.34 -123.45 -1234.56 -12345.67 -123456.78 -1234567.89 -12345678.9
-1.0
'123456789')
for datum in ${data[@]}; do
result=$(commaize $datum)
idx=$(($idx + 1))
printf "%4d %20s %20s\n" $idx $datum $result
done
datum="12345678,9"
result=$(commaize $datum -1 '.' ',')
idx=$(($idx + 1))
printf "%4d %20s %20s # EU style\n" $idx $datum $result
data=(12345.123 12345 12345.1251)
for datum in ${data[@]}; do
result=$(commaize $datum 2)
idx=$(($idx + 1))
printf "%4d %20s %20s # 2 decimal places\n" $idx $datum $result
done
datum=1234567.89
result=$(commaize $datum 1)
idx=$(($idx + 1))
printf "%4d %20s %20s # 1 decimal place\n" $idx $datum $result
↑ C++ Implementation
This is the C++ implementation. It uses C++-11 constructs so it will not work on platforms that do not have a recent C++ compiler. In my case I had to install an updated version of the C++ compiler to handle C++-11 constructs from http://joelinoff.com/blog/?p=1003.
This is how I compiled it.
$ # First install the compiler. The default is too old.
$ # You can skip this step if you have newer version.
$ cd ~/work
$ mkdir -p gcc/4.8.2
$ cd gcc/4.8.2
$ wget http://projects.joelinoff.com/gcc-4.8.2./Makefile
<output snipped>
$ wget http://projects.joelinoff.com/gcc-4.8.2./bld.sh
<output snipped>
$ make
<output snipped>
$ # Now compile your program.
$ cd ~/work/commaize
$ CXX_RTFDIR=~/work/gcc/4.8.2/rtf
$ export PATH="${CXX_RTFDIR}/bin:${PATH}"
$ export LD_LIBRARY_PATH="$(CXX_RTFDIR)/lib:$(CXX_RTFDIR)/lib64:${LD_LIBRARY_PATH}"
$ export LD_RUN_PATH="${CXX_RTFDIR}/lib:${CXX_RTFDIR}/lib64:${LD_LIBRARY_PATH}"
$ g++ --version # make sure that you have the correct version
<output snipped>
$ g++ -g -Wall -std=c++11 -o commaize.exe commaize.cc
<output snipped>
$ # Run it.
$ ./commaize.exe
<output snipped>
This is the source code.
// ================================================================
// Commaize a number.
// ================================================================
#include <cstdlib>
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;
template <typename T> string commaize_tostring(T in) {return to_string(in);}
string commaize_tostring(const string& in) {return in;}
/**
* Insert thousands separator into a number and, optionally, specify
* the number of decimal places.
*
* Here are some examples that demonstrate how it is used.
*
* string x;
* x=commaize(1234) # 1,234
* x=commaize(-1234) # -1,234
* x=commaize(12345.678) # 12,345.678
* x=commaize(-12345.678) # -12,345.678
* x=commaize(12345.678, dpl=2) # 12,345.69
* x=commaize(12345.678, dpl=1) # 12,345.7
* x=commaize(1234,sep='.') # 1.234
* x=commaize("1234567,89", sep='.', dpt=',') # EU: 1.234.567,89
*
* The arguments are:
*
* @param num The number: int, float or string.
* @param dpl Decimal places (default: -1 -> do nothing).
* @param sep Thousands separator (default: ",").
* @param dpt Decimal point (default: ".").
* @returns a formatted string.
*/
template <typename T>
string commaize(T num, int dpl=-1, char sep=',', char dpt='.')
{
string characteristic = commaize_tostring(num);
if (dpl >= 0) {
// Use sprintf() to do the rounding.
double val = atof(characteristic.c_str());
char fmt[128];
char tmp[128];
sprintf(fmt, "%%.%df", dpl);
sprintf(tmp, fmt, val);
characteristic = tmp;
}
string neg="";
if (characteristic[0] == '-') {
neg = "-";
characteristic = characteristic.substr(1, string::npos);
}
string mantissa = "";
size_t pos = characteristic.find(dpt);
if (pos != string::npos) {
// There is a decimal point, break out
// the characteristic and the mantissa
// because we only care about the
// characteristic for commas. We will
// append the mantissa at the end.
mantissa = characteristic.substr(pos+1);
// Trim trailing zeros.
if (dpl < 0) {
while (mantissa.size() > 0 && mantissa[mantissa.size()-1] == '0') {
mantissa = mantissa.substr(0, mantissa.size() - 1);
}
}
characteristic = characteristic.substr(0, pos);
}
// Insert the commas.
size_t j = characteristic.size() % 3;
string result = "";
result.reserve(characteristic.size() + ((characteristic.size()/3) + 2 + mantissa.size()));
for(size_t i=0; i<characteristic.size(); i++) {
if ((i%3) == j && i > 0) {
result += sep;
}
result += characteristic[i];
}
if (!mantissa.empty()) {
char dpts[2] = {dpt, 0};
result += string(dpts) + mantissa;
}
return neg + result;
}
/**
* main
*/
int main()
{
string result;
string str;
unsigned id = 0;
// Test positive integers.
size_t nums[] = {1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789};
for(auto num : nums) {
result = commaize(num);
cout << setw(4) << right << ++id << " "
<< right << setw(20) << num
<< " "
<< right << setw(20) << result
<< endl;
}
// Test positive doubles.
double dnums[] = {1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9, 1.0};
for(auto dnum : dnums) {
result = commaize(dnum);
cout << setw(4) << right << ++id << " "
<< right << fixed << setw(20) << dnum
<< " "
<< right << setw(20) << result
<< endl;
}
// Test negative integers.
for(long num: nums) {
num = -num;
result = commaize(num);
cout << setw(4) << right << ++id << " "
<< right << setw(20) << num
<< " "
<< right << setw(20) << result
<< endl;
}
// Test negative doubles.
for(auto num: dnums) {
num = -num;
result = commaize(num);
cout << setw(4) << right << ++id << " "
<< right << fixed << setprecision(2) << setw(20) << num
<< " "
<< right << setw(20) << result
<< endl;
}
// String test.
str = "123456789";
result = commaize(str);
cout << setw(4) << right << ++id << " "
<< right << setw(20) << str
<< " "
<< right << setw(20) << result
<< endl;
// EU style
str = "12345678,9";
result = commaize(str, -1, '.', ',');
cout << setw(4) << right << ++id << " "
<< right << fixed << setprecision(2) << setw(20) << str
<< " "
<< right << setw(20) << result
<< " # EU style"
<< endl;
// dollarize
// Round to 2 significant digits.
double ddata[] = {12345.123, 12345, 12345.1251};
for(auto datum : ddata) {
result = commaize(datum, 2);
cout << setw(4) << right << ++id << " "
<< right << fixed << setw(20) << datum
<< " "
<< right << setw(20) << result
<< " # 2 decimal places"
<< endl;
}
// 1 decimal place.
string datum = "1234567.89";
result = commaize(datum, 1);
cout << setw(4) << right << ++id << " "
<< right << fixed << setw(20) << datum
<< " "
<< right << setw(20) << result
<< " # 1 decimal place"
<< endl;
}
↑ Java Implementation
This is the java implementation. You compile it as follows:
$ javac Commaize.java <output snipped> $ java Commaize <output snipped>
Here is the source code.
// Commaize a number.
import java.util.*;
public class Commaize {
/**
* doubleToString
*/
public static String doubleToString(double in) {
Double d = in;
int di = d.intValue();
if (d == di) {
// Optimization for easy floats.
return String.format("%d", di);
}
String floatnum = String.format("%f", d);
// Chop off trailing zeros.
int i = floatnum.length() - 1;
while (i >= 0 && floatnum.charAt(i) == '0') {
i--;
}
floatnum = floatnum.substring(0, i+1);
// Chop off the period if there is no mantissa.
i = floatnum.length() - 1;
if (i >= 0 && floatnum.charAt(i) == '.') {
floatnum = floatnum.substring(0, i);
}
return floatnum;
}
/**
* Insert thousands separator into a number and, optionally, specify
* the number of decimal places.
*
* Here are some examples that demonstrate how it is used.
*
* string x;
* x=commaize(1234) # 1,234
* x=commaize(-1234) # -1,234
* x=commaize(12345.678) # 12,345.678
* x=commaize(-12345.678) # -12,345.678
* x=commaize(12345.678, 2) # 12,345.69
* x=commaize(12345.678, 1) # 12,345.7
* x=commaize(1234, -1, '.') # 1.234
* x=commaize("1234567,89", -1, '.', ',') # EU: 1.234.567,89
*
* The arguments are:
*
* @param num The number: int, float or string.
* @param dpl Decimal places (default: -1 -> do nothing).
* @param sep Thousands separator (default: ",").
* @param dpt Decimal point (default: ".").
* @returns a formatted string.
*/
public static String commaize(T num, int dpl, char sep, char dpt) {
String rep = num.toString();
if (rep.indexOf('E') >= 0) {
rep = doubleToString(Double.parseDouble(rep));
}
if (dpl > 0) {
// Take advantage of built in rounding.
String fmt = String.format("%%.%df", dpl);
double value = Double.parseDouble(rep);
rep = String.format(fmt, value);
}
// Get the parts of the number (characteristic and mantissa).
// 123.45
// ^ ^^
// | |+--- mantissa
// | +---- decimal point
// +------- characteristic
String characteristic = "";
String mantissa = "";
boolean past_dpt = true;
for (int i=0; i= 0 && mantissa.charAt(i) == '0') {
i--;
}
mantissa = mantissa.substring(0, i+1);
i = mantissa.length() - 1;
if (i >= 0 && mantissa.charAt(i) == dpt) {
// Eliminate the decimal point separater if there are no
// trailing values (e.g. "1.").
mantissa = "";
}
}
// Handle negative numbers.
String neg = "";
if (characteristic.charAt(0) == '-') {
neg = "-";
characteristic = characteristic.substring(1);
}
// Insert the command moving from left to right.
String result = "";
int offset = characteristic.length() % 3; // offset to first comma
for(int i=0; i 0)) {
result += sep;
}
result += characteristic.charAt(i);
}
rep = neg + result + mantissa;
return rep;
}
public static String commaize(T num) {
return commaize(num, -1, ',', '.');
}
public static String commaize(T num, int dpl) {
return commaize(num, dpl, ',', '.');
}
/**
* main.
*/
public static void main(String[] args) {
int id = 0;
String result;
String str;
String msg;
// Test positive integers.
int inums[] = new int[] {1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789};
for(int val : inums) {
result = commaize(val);
msg = String.format("%4d %20d %20s", ++id, val, result);
System.out.println(msg);
}
// Test positive floating points.
double dnums[] = new double[] {1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9, 1.0};
for(double val : dnums) {
str = doubleToString(val);
result = commaize(val);
msg = String.format("%4d %20s %20s", ++id, str, result);
System.out.println(msg);
}
// Test negative integers.
for(int val : inums) {
int nval = -val;
result = commaize(nval);
msg = String.format("%4d %20d %20s", ++id, nval, result);
System.out.println(msg);
}
// Test negative floating points.
for(double val : dnums) {
double nval = -val;
result = commaize(nval);
str = doubleToString(nval);
msg = String.format("%4d %20s %20s", ++id, str, result);
System.out.println(msg);
}
// String test.
str = "123456789";
result = commaize(str);
msg = String.format("%4d %20s %20s", ++id, str, result);
System.out.println(msg);
// EU style
str = "12345678,9";
result = commaize(str, -1, '.', ',');
msg = String.format("%4d %20s %20s", ++id, str, result);
System.out.println(msg);
// dollarize
// Round to 2 significant digits.
double ddata[] = new double[] {12345.123, 12345, 12345.1251};
for(double val : ddata) {
str = doubleToString(val);
result = commaize(val, 2);
msg = String.format("%4d %20s %20s", ++id, str, result);
System.out.println(msg);
}
// 1 decimal place
str = "1234567.89";
result = commaize(str, 1);
msg = String.format("%4d %20s %20s", ++id, str, result);
System.out.println(msg);
}
}
↑ Javascript Implementation
This is the javascript implementation. It consists of two parts: an HTML wrapper and the javascript code. You can run it in your browser by navigating to the HTML page or you can use a local javascript testing environment like rhino.
When you run it in your browser the output looks like this:
Here is the HTML source code.
<!DOCTYPE HTML>
<html>
<head>
<title>commaize</title>
<script type="text/javascript" src="commaize.js"></script>
</head>
<body>
<div id="place">
</div>
</body>
</html>
Here is the javascript source code. Note that this implementation deliberately avoids the use of libraries like jquery. For a production system you definitely want to use javascript libraries to make your code easier to read and more maintainable.
// This is raw javascript on purpose.
window.onload = function() {
// I would normally recommend using jquery ready() but since I
// want to keep this example as simple as possible, jquery is
// assumed to not be available.
// ================================================================
// Test a condition and report a message.
// ================================================================
function assert(condition, description)
{
if(!condition) {
alert('ASSERTION: ' + description + ': ' + condition);
}
}
// ================================================================
// Commaize a number.
// Insert thousands separator into a number and, optionally, specify
// the number of decimal places.
//
// Here are some examples that demonstrate how it is used.
//
// x=commaize(1234) # 1,234
// x=commaize(-1234) # -1,234
// x=commaize(12345.678) # 12,345.678
// x=commaize(-12345.678) # -12,345.678
// x=commaize(12345.678, 2) # 12,345.69
// x=commaize(12345.678, 1) # 12,345.7
// x=commaize(1234, -1, '.') # 1.234
// x=commaize("1234567,89", -1, '.', ',') # EU: 1.234.567,89
//
// The arguments are:
//
// @param num The number: int, float or string.
// @param dpl Decimal places (default: -1 -> do nothing).
// @param sep Thousands separator (default: ",").
// @param dpt Decimal point (default: ".").
// @returns a formatted string.
// ================================================================
function commaize(num, dpl=-1, sep=',', dpt='.')
{
var snum = num.toString();
assert(snum.indexOf(sep) < 0, 'found "' + sep + '" in "' + snum + '"');
// Format to N decimal places.
if (dpl > 0) {
snum = parseFloat(snum).toFixed(dpl).toString();
}
// Split into parts.
var parts = snum.split(dpt); // 123 or 123.45
assert( parts.length < 3, 'too many parts: ' + parts.length);
// Get the characteristic and mantissa.
// 123.45
// ^ ^^
// | |+--- mantissa
// | +---- decimal point
// +------- characteristic
var characteristic = parts[0];
var mantissa = parts.length == 1 ? '' : dpt + parts[1];
// Handle negative numbers.
var neg = '';
if (characteristic[0] == '-') {
neg = '-';
characteristic = characteristic.slice(1); // strip off the negative
}
// Clean up the mantissa. Remove trailing zeros unless a specific
// number of decimal places (dpl) was specified.
// 1.000 > 1
if (mantissa.length && dpl < 0) {
// Make sure that 1.0 --> 1
while (mantissa.slice(-1) == '0') {
mantissa = mantissa.slice(0, -1);
}
if (mantissa == dpt) {
mantissa = "";
}
}
// Insert the commas moving from left to right.
var result = ''; // where to put the result
var offset = characteristic.length % 3 // offset to start inserting commas
for(var i=0; i<characteristic.length; i++) {
if ((i%3) == offset && i) {
// if we are at the digit where a separator needs to be
// pre-pended, prepend the separator.
result += sep ;
}
result += characteristic[i];
}
return neg + result + mantissa;
}
// ================================================================
// main
// ================================================================
function main()
{
// This function will generate a table on the page with the
// filled in contents of the commaize function for the examples
// that we are interested in.
var rowidx = 0;
row = function(val, result, msg='')
{
var s1 = "" + rowidx;
var s2 = "" + val;
var s3 = "" + result;
var html = '';
rowidx++;
// Pad.
while (s1.length < 4) {s1 = ' ' + s1;}
while (s2.length < 20) {s2 = ' ' + s2;}
while (s3.length < 20) {s3 = ' ' + s3;}
html += s1 + ' ' + s2 + ' ' + s3;
if (msg.length > 0) {
html += ' ' + msg;
}
html += '\n';
return html;
}
var place = document.getElementById('place');
var html = '';
html += '<pre>\n';
html += 'BEGIN\n';
// Rows of data here.
var data = [1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789,
1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9,
1.0,
-1, -12, -123, -1234, -12345, -123456, -1234567, -12345678, -123456789,
-1.23, -12.34, -123.45, -1234.56, -12345.67, -123456.78, -1234567.89, -12345678.9,
-1.0,
'123456789',
];
for(var i=0; i<data.length; i++) {
var datum = data[i];
var result = commaize(datum);
html += row(datum, result);
}
// EU style
var datum = "12345678,9";
var result = commaize(datum, -1, '.', ','); // EU style
html += row(datum, result, '# EU style');
// dollarize
// Round to 2 significant digits.
var data1 = [12345.123, 12345, 12345.1251,];
for(var i=0; i<data1.length; i++) {
datum = data1[i];
result = commaize(datum, 2);
html += row(datum, result, '# 2 decimal places');
}
// 1 decimal place
datum = "1234567.89";
result = commaize(datum, 1)
html += row(datum, result, '# 1 decimal place');
html += 'END\n';
html += '</pre>\n';
place.innerHTML = html;
}
main();
}
↑ Perl Implementation
This is the perl implementation.
#!/usr/bin/env perl
#
# Example that shows how to insert commas into a number.
#
use strict;
use warnings;
&main;
# ================================================================
# main
# ================================================================
sub main
{
my @data = (1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789,
1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9,
1.0,
-1, -12, -123, -1234, -12345, -123456, -1234567, -12345678, -123456789,
-1.23, -12.34, -123.45, -1234.56, -12345.67, -123456.78, -1234567.89, -12345678.9,
-1.0,
'123456789',
);
my $idx = 1;
foreach my $datum (@data) {
my $result = &commaize($datum);
printf("%4d %20s %20s\n", $idx++, $datum, $result);
}
# EU style
my $datum = "12345678,9";
my $result = &commaize($datum, -1, ".", ","); # EU style
printf("%4d %20s %20s # EU style\n", $idx++, $datum, $result);
# dollarize
# Round to 2 significant digits.
my @ddata = (12345.123, 12345, 12345.1251,);
for $datum (@ddata) {
$result = &commaize($datum, 2);
printf("%4d %20s %20s # 2 decimal places\n", $idx++, $datum, $result);
}
# 1 decimal place
$datum = "1234567.89";
$result = &commaize($datum, 1);
printf("%4d %20s %20s # 1 decimal place\n",$idx++, $datum, $result);
}
# ================================================================
# Insert thousands separator into a number and, optionally, specify
# the number of decimal places.
#
# Here are some examples that demonstrate how it is used.
#
# x=commaize(1234) # 1,234
# x=commaize(-1234) # -1,234
# x=commaize(12345.678) # 12,345.678
# x=commaize(-12345.678) # -12,345.678
# x=commaize(12345.678, 2) # 12,345.69
# x=commaize(12345.678, 1) # 12,345.7
# x=commaize(1234, -1, ".") # 1.234
# x=commaize("1234567,89", -1, ".", ",") # EU: 1.234.567,89
#
# The arguments are:
#
# @param num The number: int, float or string.
# @param dpl Decimal places (default: -1 -> do nothing).
# @param sep Thousands separator (default: ",").
# @param dpt Decimal point (default: ".").
# @returns a formatted string.
# ================================================================
sub commaize
{
my $num = shift;
my $dpl = shift;
my $sep = shift;
my $dpt = shift;
$dpl = -1 if( !defined($dpl) );
$sep = "," if( !defined($sep) );
$dpt = "." if( !defined($dpt) );
# Format to N decimal places.
my $rep = $num;
if ($dpl >= 0) {
my $fmt = "%.${dpl}f";
$rep = sprintf($fmt, $num);
}
# Split into parts.
# Using split is tricky here because the split value is unknown,
# i work around that using this simple brute force approach.
my @parts = ();
my @chars = split(//, $rep);
my $idx = 0;
for my $char (@chars) {
if ($char eq $dpt) {
$idx++;
}
else {
if ($#parts <= $idx) {
push(@parts, "");
}
$parts[$idx] .= $char;
}
}
# Get the characteristic and mantissa.
# 123.45
# ^ ^^
# | |+--- mantissa
# | +---- decimal point
# +------- characteristic
my $characteristic = $parts[0];
my $mantissa = "";
$mantissa = "$dpt$parts[1]" if $#parts > 1;
# Handle negative numbers.
my $neg = "";
if (substr($characteristic, 0, 1) eq "-") {
$neg = "-";
$characteristic = substr($characteristic, 1);
}
# Insert the commas moving from left to right.
my $result = ""; # where to put the result
my $offset = length($characteristic) % 3; # offset to start inserting commas
for(my $i=0; $i<length($characteristic); $i++) {
if (($i % 3) == $offset && $i) {
$result .= $sep;
}
$result .= substr($characteristic, $i, 1);
}
my $final = $neg . $result . $mantissa;
return $final;
}
↑ PHP Implementation
This is the PHP implementation. This is how you run it.
$ php -f commaize.php
This is the source code.
<?php
#
# Example that shows how to insert commas into a number.
#
# ================================================================
# Insert thousands separator into a number and, optionally, specify
# the number of decimal places.
#
# Here are some examples that demonstrate how it is used.
#
# x=commaize(1234) # 1,234
# x=commaize(-1234) # -1,234
# x=commaize(12345.678) # 12,345.678
# x=commaize(-12345.678) # -12,345.678
# x=commaize(12345.678, 2) # 12,345.69
# x=commaize(12345.678, 1) # 12,345.7
# x=commaize(1234, -1, ".") # 1.234
# x=commaize("1234567,89", -1, ".", ",") # EU: 1.234.567,89
#
# The arguments are:
#
# @param num The number: int, float or string.
# @param dpl Decimal places (default: -1 -> do nothing).
# @param sep Thousands separator (default: ",").
# @param dpt Decimal point (default: ".").
# @returns a formatted string.
# ================================================================
function commaize($num, $dpl=-1, $sep=",", $dpt=".") {
$rep = $num;
# Format to N decimal places.
if ($dpl >= 0) {
$fmt = "%.${dpl}f";
$rep = sprintf($fmt, $num);
}
# Split into parts.
$parts = explode($dpt, $rep);
# Get the characteristic and mantissa.
# 123.45
# ^ ^^
# | |+--- mantissa
# | +---- decimal point
# +------- characteristic
$characteristic = $parts[0];
$mantissa = count($parts) == 1 ? "" : $dpt . $parts[1];
# Handle negative numbers.
$neg = "";
if ($characteristic[0] == "-") {
$neg = "-";
$characteristic = substr($characteristic, 1);
}
# Clean up the mantissa. Remove trailing zeros unless a specific
# number of decimal places (dpl) was specified.
# 1.000 > 1
if (strlen($mantissa) and $dpl < 0) {
# Make sure that 1.0 --> 1
$mantissa = rtrim($mantissa, "0");
$mantissa = rtrim($mantissa, $dpt);
}
# Insert the commas moving from left to right.
$result = ""; # where to put the result
$offset = strlen($characteristic) % 3; # offset to start inserting commas
for($i=0; $i<strlen($characteristic); $i++) {
if (($i%3) == $offset and $i > 0) {
$result .= $sep;
}
$result .= $characteristic[$i];
}
$final = $neg . $result . $mantissa;
return $final;
}
# ================================================================
# main
# ================================================================
function main() {
$data = array (1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789,
1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9,
1.0,
-1, -12, -123, -1234, -12345, -123456, -1234567, -12345678, -123456789,
-1.23, -12.34, -123.45, -1234.56, -12345.67, -123456.78, -1234567.89, -12345678.9,
-1.0,
'123456789',
);
$idx = 1;
foreach($data as $datum) {
$result = commaize($datum);
echo sprintf("%4d %20s %20s\n", $idx++, "$datum", $result);
}
# EU style
$datum = "12345678,9";
$result = commaize($datum, -1, ".", ",");
echo sprintf("%4d %20s %20s # EU style\n", $idx++, "$datum", $result);
# dollarize
# Round to 2 significant digits.
$data = array(12345.123, 12345, 12345.1251,);
foreach($data as $datum) {
$result = commaize($datum, 2);
echo sprintf("%4d %20s %20s # 2 decimal places\n", $idx++, "$datum", $result);
}
# 1 decimal place
$datum = "1234567.89";
$result = commaize($datum, 1);
echo sprintf("%4d %20s %20s # 1 decimal place\n", $idx++, "$datum", $result);
}
main();
?>
↑ Python Implementation
This is the python implementation. Note that I use asserts because they are available natively.
#!/usr/bin/env python
'''
Example that shows how to insert commas into a number.
'''
import re
def commaize(num, dpl=-1, sep=',', dpt='.'):
'''
Insert thousands separator into a number and, optionally, specify
the number of decimal places.
Here are some examples that demonstrate how it is used.
x=commaize(1234) # 1,234
x=commaize(-1234) # -1,234
x=commaize(12345.678) # 12,345.678
x=commaize(-12345.678) # -12,345.678
x=commaize(12345.678, dpl=2) # 12,345.69
x=commaize(12345.678, dpl=1) # 12,345.7
x=commaize(1234,sep='.') # 1.234
x=commaize("1234567,89", sep='.', dpt=',') # EU: 1.234.567,89
The arguments are:
@param num The number: int, float or string.
@param dpl Decimal places (default: -1 -> do nothing).
@param sep Thousands separator (default: ",").
@param dpt Decimal point (default: ".").
@returns a formatted string.
'''
rep = str(num) # convert the int or float number to a string
assert rep.find(sep) < 0 # no separators allowed
# Format to N decimal places.
if dpl >= 0:
fmt = '%.' + str(dpl) + 'f'
rep = fmt % (float(num))
# Split into parts.
parts = rep.split(dpt) # 123 or 123.45
assert len(parts) < 3 # multiple dpts not allowed
# Get the characteristic and mantissa.
# 123.45
# ^ ^^
# | |+--- mantissa
# | +---- decimal point
# +------- characteristic
characteristic = parts[0]
mantissa = "" if len(parts) == 1 else dpt + parts[1]
# Handle negative numbers.
neg = ''
if characteristic[0] == '-':
neg = '-'
characteristic = characteristic[1:] # strip off the negative
# Check our assumption that both the characteristic and the mantissa
# are integers at this point.
assert re.match(r'^\d+$', characteristic) # empty strings are not allowed
if len(mantissa): # empty strings are allowed
assert re.match(r'^' + dpt + '\d+$', mantissa)
# Clean up the mantissa. Remove trailing zeros unless a specific
# number of decimal places (dpl) was specified.
# 1.000 > 1
if len(mantissa) and dpl < 0:
# Make sure that 1.0 --> 1
mantissa = mantissa.rstrip('0')
mantissa = mantissa.rstrip(dpt)
# Insert the commas moving from left to right.
result = '' # where to put the result
offset = len(characteristic) % 3 # offset to start inserting commas
for i in range(len(characteristic)):
if (i%3) == offset and i:
# if we are at the digit where a separator needs to be
# pre-pended, prepend the separator.
result += sep
result += characteristic[i]
final = neg + result + mantissa
return final
def main():
'''
Test the method.
'''
idx = [0]
def incidx():
idx[0] += 1
return idx[0]
data = [1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789,
1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9,
1.0,
-1, -12, -123, -1234, -12345, -123456, -1234567, -12345678, -123456789,
-1.23, -12.34, -123.45, -1234.56, -12345.67, -123456.78, -1234567.89, -12345678.9,
-1.0,
'123456789',
]
for datum in data:
result = commaize(datum)
print('%4d %20s %20s' % (incidx(), str(datum), result))
# EU style
datum = "12345678,9"
result = commaize(datum, sep='.', dpt=',') # EU style
print('%4d %20s %20s # EU style' % (incidx(), str(datum), result))
# dollarize
# Round to 2 significant digits.
data = [12345.123, 12345, 12345.1251,]
for datum in data:
result = commaize(datum, dpl=2)
print('%4d %20s %20s # 2 decimal places' % (incidx(), str(datum), result))
# 1 decimal place
datum = "1234567.89"
result = commaize(datum, dpl=1)
print('%4d %20s %20s # 1 decimal place' % (incidx(), str(datum), result))
if __name__ == '__main__':
main()
↑ Ruby Implementation
This is the ruby implementation.
#!/usr/bin/env ruby
# ================================================================
# Insert thousands separator into a number and, optionally, specify
# the number of decimal places.
#
# Here are some examples that demonstrate how it is used.
#
# x=commaize(1234) # 1,234
# x=commaize(-1234) # -1,234
# x=commaize(12345.678) # 12,345.678
# x=commaize(-12345.678) # -12,345.678
# x=commaize(12345.678, 2) # 12,345.69
# x=commaize(12345.678, 1) # 12,345.7
# x=commaize(1234, -1, '.') # 1.234
# x=commaize("1234567,89", -1, '.', ',') # EU: 1.234.567,89
#
# The arguments are:
#
# @param num The number: int, float or string.
# @param dpl Decimal places (default: -1 -> do nothing).
# @param sep Thousands separator (default: ",").
# @param dpt Decimal point (default: ".").
# @returns a formatted string.
# ================================================================
def commaize(num, dpl=-1, sep=',', dpt='.')
snum = num.to_s
raise "invalid number format" unless snum.count(sep.to_s) == 0
# Format to N decimal places.
if (dpl >= 0) then
fmt = '%.' << dpl.to_i.to_s << 'f'
snum = sprintf fmt, snum.to_f
end
# Split into parts.
parts = snum.split(dpt)
raise "invalid number of parts: %d" % (parts.length) unless parts.length < 3
# Get the characteristic and mantissa.
# 123.45
# ^ ^^
# | |+--- mantissa
# | +---- decimal point
# +------- characteristic
characteristic = parts[0]
mantissa = parts.length > 1 ? dpt + parts[1] : ""
# Handle negative numbers.
neg = ""
if (characteristic[0] == '-')
characteristic = characteristic[1..-1]
neg = "-"
end
# Clean up the mantissa. Remove trailing zeros unless a specific
# number of decimal places (dpl) was specified.
# 1.000 > 1
if (mantissa.length > 0 and dpl < 0) then
# Make sure that 1.0 --> 1
while (mantissa[-1] == '0')
mantissa = mantissa.chomp("0")
end
mantissa = mantissa.chomp(dpt)
end
# Insert the commas moving from left to right.
result = ""
j = characteristic.length % 3
characteristic.split("").each_with_index do |item, i|
if ((i%3) == j and i > 0) then
# if we are at the digit where a separator needs to be
# pre-pended, prepend the separator.
result << sep
end
result << item
end
if (mantissa.length) then
result << mantissa
end
return neg + result
end
# ================================================================
# Test method.
# ================================================================
def test()
data = [1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789,
1.23, 12.34, 123.45, 1234.56, 12345.67, 123456.78, 1234567.89, 12345678.9,
1.0,
-1, -12, -123, -1234, -12345, -123456, -1234567, -12345678, -123456789,
-1.23, -12.34, -123.45, -1234.56, -12345.67, -123456.78, -1234567.89, -12345678.9,
-1.0,
'123456789',
]
idx = 1
incidx = lambda do
idx += 1
return idx
end
data.each do |num|
result = commaize(num)
print("%4d %20s %20s\n" % [incidx.call(), num.to_s, result])
end
# EU style
datum = "12345678,9"
result = commaize(datum, -1, '.', ',') # EU style
print("%4d %20s %20s # EU style\n" % [incidx.call(), datum.to_s, result])
# dollarize
# Round to 2 significant digits.
data = [12345.123, 12345, 12345.1251,]
data.each do |num|
result = commaize(num, 2)
print("%4d %20s %20s # 2 decimal places\n" % [incidx.call(), num.to_s, result])
end
# 1 decimal place
datum = "1234567.89"
result = commaize(datum, dpl=1)
print("%4d %20s %20s # 1 decimal place\n" % [incidx.call(), datum.to_s, result])
end
if __FILE__ == $0
test()
end
↑ Tcl Implementation
This is the tcl implementation.
#!/usr/bin/env tclsh
#
# Example that shows how to insert commas into a number.
#
# ================================================================
# Insert thousands separator into a number and, optionally, specify
# the number of decimal places.
#
# Here are some examples that demonstrate how it is used.
#
# set x [commaize 1234] # 1,234
# set x [commaize -1234] # -1,234
# set x [commaize 12345.678] # 12,345.678
# set x [commaize -12345.678] # -12,345.678
# set x [commaize 12345.678 2] # 12,345.69
# set x [commaize 12345.678 1] # 12,345.7
# set x [commaize 1234 -1 "."] # 1.234
# set x [commaize "1234567,89" "." ","] # EU: 1.234.567,89
#
# The arguments are:
#
# @param num The number: int, float or string.
# @param dpl Decimal places (default: -1 -> do nothing).
# @param sep Thousands separator (default: ",").
# @param dpt Decimal point (default: ".").
# @returns a formatted string.
# ================================================================
proc commaize {num {dpl -1} {sep ","} {dpt "."}} {
set rep $num
# Format to N decimal places.
if {$dpl >= 0} {
set fmt "%.${dpl}f"
set rep [format $fmt $num]
}
# Split into parts.
set parts [split $rep $dpt]
set size [llength $parts]
# Get the characteristic and mantissa.
# 123.45
# ^ ^^
# | |+--- mantissa
# | +---- decimal point
# +------- characteristic
set characteristic [lindex $parts 0]
if {$size == 1} {
set mantissa ""
} else {
set num [lindex $parts 1]
set mantissa "${dpt}${num}"
}
# Trim off trailing zeros.
if {$dpl < 0} {
set mantissa [string trimright $mantissa "0"]
set mantissa [string trimright $mantissa $dpt]
}
# Handle negative numbers.
set neg [string index $characteristic 0]
if {[string compare $neg "-"] == 0} {
# strip off the negative
set characteristic [string range $characteristic 1 [string length $characteristic]]
} else {
set neg ""
}
# Insert the commas moving from left to right.
set result ""
set offset [expr [string length $characteristic] % 3]
for {set i 0} {$i < [string length $characteristic]} {incr i} {
if {[expr $i>0] && [expr ($i%3)] == $offset} {
append result $sep
}
append result [string index $characteristic $i]
}
set final "${neg}${result}${mantissa}"
return $final
}
# ================================================================
# main
# ================================================================
proc main {} {
set data [list 1 12 123 1234 12345 123456 1234567 12345678 123456789 \
1.23 12.34 123.45 1234.56 12345.67 123456.78 1234567.89 12345678.9 \
1.0 \
-1 -12 -123 -1234 -12345 -123456 -1234567 -12345678 -123456789 \
-1.23 -12.34 -123.45 -1234.56 -12345.67 -123456.78 -1234567.89 -12345678.9 \
-1.0 \
"123456789" \
]
set idx 0
foreach datum $data {
incr idx
set result [commaize $datum]
set f [format "%4d %20s %20s" $idx $datum $result]
puts $f
}
# EU style
set datum "12345678,9"
incr idx
set result [commaize $datum -1 "." ","]
set f [format "%4d %20s %20s" $idx $datum $result]
puts $f
# dollarize
# Round to 2 significant digits
set data [list 12345.123 12345 12345.1251]
foreach datum $data {
incr idx
set result [commaize $datum 2]
set f [format "%4d %20s %20s" $idx $datum $result]
puts $f
}
# 1 decimal place
set datum "1234567.89"
incr idx
set result [commaize $datum 1]
set f [format "%4d %20s %20s" $idx $datum $result]
puts $f
}
main
↑ Tested Platforms
I have tested this on the following platforms. In all cases I had to install an updated versions of the C++ compiler to handle C++-11 constructs from http://joelinoff.com/blog/?p=1003.
| Platform | Status> | Notes |
|---|---|---|
| CentOS 5.5 | Passed | Required installion of g++ 4.8.2 |
| CentOS 5.8 | Passed | Required installation of g++ 4.8.2 |
| CentOS 6.5 | Passed | Required installation of g++ 4.8.2 |
| Mac OS X 10.9.2 | Passed | Required installation of g++ 4.8.2 |
↑ License
This software is licensed under the MIT open source license.
Copyright (c) 2014 by Joe Linoff
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Enjoy!
