diff --git a/.npmignore b/.npmignore index 9daeafb..e419d80 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1,2 @@ test +benchmark/ diff --git a/README.md b/README.md index 714c23d..a2eeea7 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,21 @@ var commaNumber = require('comma-number') commaNumber(1000) // "1,000" commaNumber(-1000) // "-1,000" commaNumber(-1000, '.') // "-1.000" + +commaNumber(1000.12) // "1,000.12" +commaNumber(-1000.12) // "-1,000.12" +commaNumber('-1000,12', '.', ',') // "-1.000,12" ``` ## API -### commaNumber(number, [separator=',']) +### commaNumber(number, [separator=','], [decimalChar='.']) **Parameters:** * number : {(Number|String)} Number to format * separator : {String} Value used to separate numbers +* decimalChar : {String} Value used to separate the decimal value **Returns:** diff --git a/benchmark/original.js b/benchmark/original.js new file mode 100644 index 0000000..10bc5f8 --- /dev/null +++ b/benchmark/original.js @@ -0,0 +1,45 @@ +/** + * Comma number formatter + * @param {Number} number Number to format + * @param {String} [separator=','] Value used to separate numbers + * @returns {String} Comma formatted number + */ +module.exports = function commaNumber (number, separator) { + separator = typeof separator === 'undefined' ? ',' : ('' + separator) + + // Convert to number if it's a non-numeric value + if (typeof number !== 'number') { + number = Number(number) + } + + // NaN => 0 + if (isNaN(number)) { + number = 0 + } + + // Return Infinity immediately + if (!isFinite(number)) { + return '' + number + } + + var stringNumber = ('' + Math.abs(number)) + .split('') + .reverse() + + var result = [] + for (var i = 0; i < stringNumber.length; i++) { + if (i && i % 3 === 0) { + result.push(separator) + } + result.push(stringNumber[i]) + } + + // Handle negative numbers + if (number < 0) { + result.push('-') + } + + return result + .reverse() + .join('') +} diff --git a/benchmark/run.js b/benchmark/run.js new file mode 100644 index 0000000..107bfeb --- /dev/null +++ b/benchmark/run.js @@ -0,0 +1,84 @@ +require('console.table') +var comma = require('../') + , Benchmark = require('benchmark') + +Benchmark.options.initCount = 10 +Benchmark.options.minSamples = 10 + +// useful when altering things because it runs once per. +// Benchmark.options.initCount = 1 +// Benchmark.options.minSamples = 1 +// Benchmark.options.minTime = -1 +// Benchmark.options.maxTime = -1 + +inputs = [ + '1', + '12', + '123', + '1234', + '12345', + '123456', + '1234567', + '12345678', + '123456789', + '1234567890', + '12345678901', + '123456789012', + + 1, + 12, + 123, + 1234, + 12345, + 123456, + 1234567, + 12345678, + 123456789, + 1234567890, + 12345678901, + 123456789012 +] + +var methods = [ + require('./original'), + comma +] + +var suite = new Benchmark.Suite + , headers = [' ', 'original', 'alternate'] + , results = [] + +for (var i = 0; i < inputs.length; i++) { + var input = inputs[i] + , name = typeof input === 'string' ? '\'' + input + '\'' : input + + results.push([name]) + + suite.add(name, methods[0].bind(null, input)) + suite.add(name, methods[1].bind(null, input)) +} + +var columns = methods.length + 1 + , row = 0 + +suite.on('cycle', function(event) { + var it = event.target + , which = results[row].length === 1 ? ' original' : 'alternate' + + console.log(which,'completed',it.name) + + results[row].push(comma(it.hz.toFixed(2)) + ' (+-' + it.stats.rme.toFixed(2) + '%)') + + if (results[row].length === columns) { + return row++ + } +}) + +suite.on('complete', function() { + console.log() + console.table(headers, results) +}) + +suite.run({ + async: false +}) diff --git a/lib/index.js b/lib/index.js index 10bc5f8..49dd800 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,44 +2,74 @@ * Comma number formatter * @param {Number} number Number to format * @param {String} [separator=','] Value used to separate numbers + * @param {String} [decimalChar='.'] Decimal character * @returns {String} Comma formatted number */ -module.exports = function commaNumber (number, separator) { - separator = typeof separator === 'undefined' ? ',' : ('' + separator) +module.exports = function commaNumber (number, separator, decimalChar) { - // Convert to number if it's a non-numeric value + // get the stringNumber based on type, or, return '0' for invalid types + var stringNumber + switch(typeof number) { + case 'string': stringNumber = number ; break + case 'number': stringNumber = String(number) ; break + default : return '0' // object, undefined, function, array, ... + } + + // strip off decimal value to append to final result at the bottom + decimalChar = typeof decimalChar === 'undefined' ? '.' : String(decimalChar) + var decimal = stringNumber.lastIndexOf(decimalChar) + if (decimal > -1) { + number = stringNumber.slice(0, decimal) + decimal = stringNumber.slice(decimal) + stringNumber = number + } else { + decimal = null + } + + // ensure we have an actual number if (typeof number !== 'number') { number = Number(number) } + // when it doesn't need a separator then return it (w/decimal, if exists) + if (-1000 < number && number < 1000) { + return decimal ? stringNumber + decimal : stringNumber + } + // NaN => 0 if (isNaN(number)) { - number = 0 + return '0' } // Return Infinity immediately - if (!isFinite(number)) { - return '' + number + if (!isFinite(number) ) { + return stringNumber } - var stringNumber = ('' + Math.abs(number)) - .split('') - .reverse() + // below here we split the number at spots to add a separator. + // then, combine it with the separator and add decimal value (if exists) + + var start = stringNumber[0] === '-' ? 1 : 0 // start after minus sign + , count = stringNumber.length - start - 1 // count digits after first + , strings = [] // hold string parts + , i = (count % 3) + 1 + start // index for first separator - var result = [] - for (var i = 0; i < stringNumber.length; i++) { - if (i && i % 3 === 0) { - result.push(separator) - } - result.push(stringNumber[i]) + // grab string content before where the first separator belongs + strings.push(stringNumber.slice(0, i)) + + // split remaining string in groups of 3 where a separator belongs + for (; i < stringNumber.length; i += 3) { + strings.push(stringNumber.substr(i, 3)) } - // Handle negative numbers - if (number < 0) { - result.push('-') + // finally, combine groups with the separator + separator = typeof separator === 'undefined' ? ',' : String(separator) + stringNumber = strings.join(separator) + + // if there's a decimal value then append it + if (decimal) { + stringNumber += decimal } - return result - .reverse() - .join('') + return stringNumber } diff --git a/package.json b/package.json index fed2852..e7ed46d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ }, "homepage": "https://github.com/cesarandreu/comma-number#readme", "devDependencies": { + "benchmark": "^2.1.2", + "console.table": "^0.7.0", "standard": "^4.5.4", "tape": "^4.0.0" } diff --git a/test/index.js b/test/index.js index d5accc8..ff06c46 100644 --- a/test/index.js +++ b/test/index.js @@ -6,43 +6,125 @@ test('Formatting', function (t) { // Positive numbers [0, '0'], [1, '1'], - [100, '100'], - [1000, '1,000'], - [10000, '10,000'], - [100000, '100,000'], - [1000000, '1,000,000'], + [12, '12'], + [123, '123'], + [1234, '1,234'], + [12345, '12,345'], + [123456, '123,456'], + [1234567, '1,234,567'], + [1234567890, '1,234,567,890'], [Infinity, 'Infinity'], + // With decimals + [.1, '0.1'], + [.12, '0.12'], + [.123, '0.123'], + [1.2, '1.2'], + [1.23, '1.23'], + [1.234, '1.234'], + [12.3, '12.3'], + [12.34, '12.34'], + [123.4, '123.4'], + [123.45, '123.45'], + [1234.5, '1,234.5'], + [1234.56, '1,234.56'], + [12345.6, '12,345.6'], + [12345.67, '12,345.67'], + [123456.7, '123,456.7'], + [123456.78, '123,456.78'], + [123456.789, '123,456.789'], + [1234567.8, '1,234,567.8'], + [1234567.89, '1,234,567.89'], + [1234567.899, '1,234,567.899'], + // Negative numbers [-1, '-1'], - [-100, '-100'], - [-1000, '-1,000'], - [-10000, '-10,000'], - [-100000, '-100,000'], - [-1000000, '-1,000,000'], + [-12, '-12'], + [-123, '-123'], + [-1234, '-1,234'], + [-12345, '-12,345'], + [-123456, '-123,456'], + [-1234567, '-1,234,567'], + [-1234567890, '-1,234,567,890'], [-Infinity, '-Infinity'], + // With decimals + [-.1, '-0.1'], + [-.12, '-0.12'], + [-.123, '-0.123'], + [-1.2, '-1.2'], + [-1.23, '-1.23'], + [-1.234, '-1.234'], + [-12.3, '-12.3'], + [-12.34, '-12.34'], + [-123.4, '-123.4'], + [-123.45, '-123.45'], + [-1234.5, '-1,234.5'], + [-1234.56, '-1,234.56'], + [-12345.6, '-12,345.6'], + [-12345.67, '-12,345.67'], + [-123456.7, '-123,456.7'], + [-123456.78, '-123,456.78'], + [-123456.789, '-123,456.789'], + [-1234567.8, '-1,234,567.8'], + [-1234567.89, '-1,234,567.89'], + [-1234567.899, '-1,234,567.899'], + // Strings ['0', '0'], ['1', '1'], - ['100', '100'], - ['1000', '1,000'], - ['10000', '10,000'], - ['100000', '100,000'], - ['1000000', '1,000,000'], + ['12', '12'], + ['123', '123'], + ['1234', '1,234'], + ['12345', '12,345'], + ['123456', '123,456'], + ['1234567', '1,234,567'], + ['1234567890', '1,234,567,890'], + + // With decimals + ['.1', '.1'], + ['0.1', '0.1'], + ['.12', '.12'], + ['0.12', '0.12'], + ['.123', '.123'], + ['0.123', '0.123'], + ['1.2', '1.2'], + ['1.23', '1.23'], + ['1.234', '1.234'], + ['12.3', '12.3'], + ['12.34', '12.34'], + ['123.4', '123.4'], + ['123.45', '123.45'], + ['1234.5', '1,234.5'], + ['1234.56', '1,234.56'], + ['12345.6', '12,345.6'], + ['12345.67', '12,345.67'], + ['123456.7', '123,456.7'], + ['123456.78', '123,456.78'], + ['123456.789', '123,456.789'], + ['1234567.8', '1,234,567.8'], + ['1234567.89', '1,234,567.89'], + ['1234567.899', '1,234,567.899'], // Invalid input [[], '0'], [{}, '0'], [NaN, '0'], [null, '0'], - [undefined, '0'] + [undefined, '0'], + ['abc', '0'] ] t.plan(testPairs.length) testPairs.forEach(function (pair) { - var str = '' + (typeof pair[0] === 'object' ? JSON.stringify(pair[0]) : pair[0]) - t.equal(commaNumber(pair[0]), pair[1], str + ' => ' + pair[1]) + var input = pair[0] + , actual = commaNumber(input) + , expected = pair[1] + , inputString = typeof input === 'object' ? JSON.stringify(input) : + typeof input === 'string' ? '\'' + input + '\'' : input + , description = inputString + ' => ' + expected + + t.equal(actual, expected, description) }) }) @@ -52,3 +134,10 @@ test('Separator', function (t) { t.equal(commaNumber(1000, ' '), '1 000', '1000 => 1 000') t.equal(commaNumber(1000, '.'), '1.000', '1000 => 1.000') }) + +test('Decimal Separator', function (t) { + t.plan(3) + t.equal(commaNumber('1234.5', undefined, '.'), '1,234.5', '1234.5 => 1,234.5') + t.equal(commaNumber('1234,5', '.', ','), '1.234,5', '1234,5 => 1.234,5') + t.equal(commaNumber('1234 5', undefined, ' '), '1,234 5', '1234 5 => 1,234 5') +})