accounting.js - pull request #8 speed test

Benchmark created by Joss on


Description

Testing speed of pull request

Preparation HTML

<script>
  /*!
   * accounting.js javascript library v0.1.3
   * https://josscrowcroft.github.com/accounting.js/
   *
   * Copyright 2011 by Joss Crowcroft
   * Licensed under GPL v3 | http://www.gnu.org/licenses/gpl-3.0.txt
   */
  var accounting = (function () {
        
        /**
         * Removes currency formatting from a number or array of numbers, returning numeric values
         * 
         * Decimal must be included in the regular expression to match floats (default: ".")
         * To do: rewrite this to be a little more elegant and maybe throw useful errors.
         */
        function unformat(number, decimal) {
                // Recursively unformat arrays:
                if (typeof number === "object" && number.length > 1) {
                        for (var i = 0, values = []; i < number.length;
                                values.push(unformat(number[i], decimal)), i++
                        );
                        return values;
                }
                
                // Fails silently (need decent errors):
                number = number || 0;
                
                // Default decimal point is "." but could be set to eg. ",":
            decimal = decimal || ".";
            
            // Build regex to strip out everything except digits, decimal point and minus sign:
                var regex = new RegExp("[^0-9-" + decimal + "]", ["g"]),
                        unformatted = parseFloat(("" + number).replace(regex, '').replace(decimal, '.'));
                
                // This will fail silently which may cause trouble, let's wait and see:
                return !isNaN(unformatted) ? unformatted : 0;
        }
        
        
        /**
         * Implementation of toFixed() that treats floats more like decimals
         * 
         * Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present 
         * problems for accounting- and finance-related software.
         */
        function toFixed(value, precision) {
                // Default precision (decimal places) is 0:
                precision = !isNaN(precision = Math.abs(precision)) ? precision : 0;
                var power = Math.pow(10, precision);
                
                // Multiply up by precision, round accurately, then divide and use native toFixed():
                return (Math.round(value * power) / power).toFixed(precision);
        }
        
        
        /**
         * Format a number, with comma-separated thousands and custom precision/decimal places
         * 
         * Localise by overriding the precision and thousand / decimal separators
         * 2nd parameter `precision` can be an object matching `settings.number`
         */
        function formatNumber(number, precision, thousand, decimal) {
                // Resursively format arrays:
                if (typeof number === "object" && number.length > 1) {
                        for (var i = 0, values = []; i < number.length;
                                // Pass parameters as-is:
                                values.push(formatNumber(number[i], precision, thousand, decimal)) && i++
                        );
                        return values;
                }
                
                // Clean up number:
                number = unformat(number);
                
                // Second param precision can be an object matching `settings.number`:
                if ( typeof precision === "object" ) {
                        // Pass object values into parameter vars (these will be checked afterwards):
                        thousand = precision.thousand;
                        decimal = precision.decimal;
                        precision = precision.precision;
                }
                
                // Make sure all parameters were set, or use defaults:
                thousand = thousand ? thousand : settings.number.thousand;
                decimal = decimal ? decimal : settings.number.decimal;
                precision = !isNaN(precision = Math.abs(precision)) ? precision : settings.number.precision;
                
                // Do some calc:
                var negative = number < 0 ? "-" : "",
                    base = parseInt(toFixed(Math.abs(number || 0), precision), 10) + "",
                    mod = base.length > 3 ? base.length % 3 : 0;
  
                // Format the number:
                return negative + (mod ? base.substr(0, mod) + thousand : "") + base.substr(mod).replace(/(\d{3})(?=\d)/g, "$1" + thousand) + (precision ? decimal + toFixed(Math.abs(number), precision).split('.')[1] : "");
        }
  
  
        /**
         * Format a number into currency
         * 
         * Usage: accounting.formatMoney(number, precision, symbol, thousandsSep, decimalSep, format)
         * defaults: (0, 2, "$", ",", ".", "%s%v")
         * 
         * Localise by overriding the symbol, precision, thousand / decimal separators and format
         * Second param can be an object matching `settings.currency` which is the easiest way.
         * 
         * To do: tidy up the parameters
         */
        function formatMoney(number, symbol, precision, thousand, decimal, format) {
                
                // Second param can be an object matching `settings.currency`:
                if (typeof symbol === "object") {
                        precision = symbol.precision;
                        thousand = symbol.thousand;
                        decimal = symbol.decimal;
                        format = symbol.format;
                        symbol = symbol.symbol;
                }
                
                // Make sure params were set, or use defaults:
                // todo: strikes me as a little bit crufty, even though it works well.
                symbol = symbol ? symbol : settings.currency.symbol;
                precision = !isNaN(precision = Math.abs(precision)) ? precision : settings.currency.precision;
                thousand = thousand ? thousand : settings.currency.thousand;
                decimal = decimal || settings.currency.decimal;
                format = format || settings.currency.format;
                
                // Format the number:
                var formatted = formatNumber(number, precision, thousand, decimal);
                
                // Return with currency symbol added:
                return format.replace('%s', symbol).replace('%v', formatted);
        }
        
        
        /**
         * Format a list of numbers into an accounting column, padding with whitespace
         * to line up currency symbols, thousand separators and decimals places
         * 
         * List should be an array of numbers
         * Second parameter can be an object containing keys that match the params
         * 
         * Returns array of accouting-formatted number strings of same length
         * 
         * NB: `white-space:pre` CSS rule is required on the list container to prevent
         * browsers from collapsing the whitespace in the output strings.
         */
        function formatColumn(list, symbol, precision, thousand, decimal) {
                if (!list) {
                        return [];
                }
                
                var maxLength = 0,
                    formatted = [],
                    i;
                
                // Format the list according to options, store the length of the longest string:
                // Performs recursive formatting of nested arrays
                for (i = 0; i < list.length; i++) {
                        if (typeof list[i] === "object") {
                                // Recursively format columns if list is a multi-dimensional array:
                                formatted.push(formatColumn(list[i], symbol, precision, thousand, decimal));
                        } else {
                                // Format this number, push into formatted list and save the length:
                                formatted.push(formatMoney(list[i], symbol, precision, thousand, decimal));
                                if (formatted[i].length > maxLength) {
                                        maxLength = formatted[i].length;
                                }
                        }
                }
                
                
                // Second param can be an object, but symbol is needed for next part, so get it:
                // tl;dr: `symbol` = default if no symbol set, or else `opts.symbol` if set, or else just `symbol`
                symbol = (!symbol ? settings.currency.symbol : symbol.symbol ? symbol.symbol : symbol);
                
                // Add space between currency symbol and number to pad strings:
                for (i = 0; i < formatted.length; i++) {
                        // Only if this is a string (not a nested array):
                        if (typeof formatted[i] === "string" && formatted[i].length < maxLength) {
                                // Match first number in string and add enough padding:
                                formatted[i] = formatted[i].replace(
                                        /(-?\d+)/,
                                        (new Array((maxLength - formatted[i].length) + 1).join(" ")) + "$1"
                                );
                        }
                }
                
                // Send back the list of numbers:                       
                return formatted;
        }
        
        
        /**
         * The library's settings configuration object
         * 
         * Contains defaults for currency and number formatting
         */
        var settings = {
                currency: {
                        symbol : "$",   // default currency symbol is '$'
                        format: "%s%v", // this controls string output: %s = symbol, %v = value/number
                        decimal : ".",  // decimal point separator
                        thousand: ",",  // thousands separator
                        precision : 2   // decimal places
                },
                number: {
                        precision : 0,  // default precision on numbers is 0
                        thousand: ",",
                        decimal : "."
                }
        };
        
        
        // Return the library's API:
        return {
                settings: settings,
                formatMoney: formatMoney,
                formatNumber: formatNumber,
                formatColumn: formatColumn,
                toFixed: toFixed,
                unformat: unformat
        };
  }());
  
  
  
  /* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
  /*!
   * accounting.js javascript library v0.1.3+
   * https://josscrowcroft.github.com/accounting.js/
   *
   * Copyright 2011 by Joss Crowcroft
   * Licensed under GPL v3 | http://www.gnu.org/licenses/gpl-3.0.txt
   */
  var accounting_miller = (function () {
        
        // SETTINGS =======
  
        /**
         * The library's settings configuration object
         * 
         * Contains defaults for currency and number formatting
         */
        var settings = {
                currency: {
                        symbol : "$",   // default currency symbol is '$'
                        format: "%s%v", // this controls string output: %s = symbol, %v = value/number
                        decimal : "."// decimal point separator
                        thousand: ","// thousands separator
                        precision : 2   // decimal places
                },
                number: {
                        precision : 0// default precision on numbers is 0
                        thousand: ",",
                        decimal : "."
                }
        };
  
  
        // HELPERS =======
  
        /**
         * Check if value is of a certain type, since typeOf isn't reliable (http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/)
         */
        function isType(val, type){
                return Object.prototype.toString.call(val) === '[object '+ type +']';
        }
  
        function isArray(val){
                return isType(val, 'Array');
        }
  
        function isObject(val){
                return isType(val, 'Object');
        }
  
        /**
         * Simplified `Array.map()`
         *
         * Returns a new Array as a result of calling `fn` on each array value.
         */
        function map(arr, fn, thisVal){
                var i = 0,
                        n = arr.length,
                        result = [];
                while(i < n){
                        result[i] = fn.call(thisVal, arr[i], i, arr);
                        i += 1;
                }
                return result;
        }
  
        /**
         * Abstract parameters normalization outside methods increasing code reuse and simplifying logic.
         */
        function normalizeParams(params, base){
                var key;
                //use `base` as reference since `params` may not contain all options
                for(key in base){
                        if(base.hasOwnProperty(key)){ //filter prototype
                                if(key === 'precision'){
                                        params[key] = normalizePrecision(params.precision, base.precision);
                                } else {
                                        params[key] = params[key] || base[key];
                                }
                        }
                }
                return params;
        }
  
        function normalizePrecision(val, base){
                val = Math.round(Math.abs(val));
                return isNaN(val)? base : val;
        }
  
        // API ======
  
        /**
         * Removes currency formatting from a number or array of numbers, returning numeric values
         * 
         * Decimal must be included in the regular expression to match floats (default: ".")
         * To do: rewrite this to be a little more elegant and maybe throw useful errors.
         */
        function unformat(number, decimal) {
                var result;
  
                if ( isArray(number) ) {
                        result = map(number, function(val){
                                return unformat(val, decimal);
                        });
                } else {
                        // Fails silently (need decent errors):
                        number = number || 0;
                        decimal = decimal || settings.number.decimal;
                        
                        // Build regex to strip out everything except digits, decimal point and minus sign:
                        var regex = new RegExp('[^0-9-' + decimal + ']', 'g'),
                                unformatted = parseFloat(('' + number).replace(regex, '').replace(decimal, '.'));
                        
                        // This will fail silently which may cause trouble, let's wait and see:
                        result = !isNaN(unformatted) ? unformatted : 0;
                }
  
                return result;
        }
        
        
        /**
         * Implementation of toFixed() that treats floats more like decimals
         * 
         * Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present 
         * problems for accounting- and finance-related software.
         */
        function toFixed(value, precision) {
                precision = normalizePrecision(precision, settings.number.precision);
                var power = Math.pow(10, precision);
                
                // Multiply up by precision, round accurately, then divide and use native toFixed():
                return (Math.round(value * power) / power).toFixed(precision);
        }
        
        
        /**
         * Format a number, with comma-separated thousands and custom precision/decimal places
         * 
         * Localise by overriding the precision and thousand / decimal separators
         * 2nd parameter `precision` can be an object matching `settings.number`
         */
        function formatNumber(number, precision, thousand, decimal) {
                var result, config;
  
                if ( isArray(number) ) {
                        result = map(number, function(val){
                                return formatNumber(val, precision, thousand, decimal);
                        });
                } else {
                
                        // Second param precision can be an object matching `settings.number`:
                        config = isObject(precision)? precision : {
                                precision: precision,
                                thousand : thousand,
                                decimal : decimal
                        };
                        config = normalizeParams(config, settings.number);
  
                        number = toFixed(unformat(number), config.precision); //limit/add decimal digits
                        
                        var parts = new RegExp('^(-?\\d{1,3})((?:\\d{3})+)(?:\\.(\\d{'+ config.precision +'}))?$').exec( number ); //separate begin [$1], middle [$2] and decimal digits [$3]
  
                        if(parts){ //number >= 1000 || number <= -1000
                                result = parts[1] + parts[2].replace(/\d{3}/g, config.thousand + '$&') + (parts[3] ? config.decimal + parts[3] : '');
                        } else {
                                result = number.replace('.', config.decimal);
                        }
                }
  
                return result;
        }
  
  
        /**
         * Format a number into currency
         * 
         * Usage: accounting.formatMoney(number, precision, symbol, thousandsSep, decimalSep, format)
         * defaults: (0, 2, "$", ",", ".", "%s%v")
         * 
         * Localise by overriding the symbol, precision, thousand / decimal separators and format
         * Second param can be an object matching `settings.currency` which is the easiest way.
         * 
         * To do: tidy up the parameters
         */
        function formatMoney(number, symbol, precision, thousand, decimal, format) {
  
                // Second param can be an object matching `settings.currency`:
                var config = isObject(symbol)? symbol : {
                        symbol : symbol,
                        precision : precision,
                        thousand : thousand,
                        decimal : decimal,
                        format : format
                };
                config = normalizeParams(config, settings.currency);
                
                var formatted = formatNumber(number, config.precision, config.thousand, config.decimal);
                
                // Return with currency symbol added:
                return config.format.replace('%s', config.symbol).replace('%v', formatted);
        }
        
        
        /**
         * Format a list of numbers into an accounting column, padding with whitespace
         * to line up currency symbols, thousand separators and decimals places
         * 
         * List should be an array of numbers
         * Second parameter can be an object containing keys that match the params
         * 
         * Returns array of accouting-formatted number strings of same length
         * 
         * NB: `white-space:pre` CSS rule is required on the list container to prevent
         * browsers from collapsing the whitespace in the output strings.
         */
        function formatColumn(list, symbol, precision, thousand, decimal) {
                if (!list) {
                        return [];
                }
                
                var maxLength = 0,
                        formatted = [],
                        i;
                
                // Format the list according to options, store the length of the longest string:
                // Performs recursive formatting of nested arrays
                for (i = 0; i < list.length; i++) {
                        if (typeof list[i] === "object") {
                                // Recursively format columns if list is a multi-dimensional array:
                                formatted.push(formatColumn(list[i], symbol, precision, thousand, decimal));
                        } else {
                                // Format this number, push into formatted list and save the length:
                                formatted.push(formatMoney(list[i], symbol, precision, thousand, decimal));
                                if (formatted[i].length > maxLength) {
                                        maxLength = formatted[i].length;
                                }
                        }
                }
                
                
                // Second param can be an object, but symbol is needed for next part, so get it:
                // tl;dr: `symbol` = default if no symbol set, or else `opts.symbol` if set, or else just `symbol`
                symbol = (!symbol ? settings.currency.symbol : symbol.symbol ? symbol.symbol : symbol);
                
                // Add space between currency symbol and number to pad strings:
                for (i = 0; i < formatted.length; i++) {
                        // Only if this is a string (not a nested array):
                        if (typeof formatted[i] === "string" && formatted[i].length < maxLength) {
                                // Match first number in string and add enough padding:
                                formatted[i] = formatted[i].replace(
                                        /(-?\d+)/,
                                        (new Array((maxLength - formatted[i].length) + 1).join(" ")) + "$1"
                                );
                        }
                }
                
                // Send back the list of numbers:                       
                return formatted;
        }       
        
        
        // Return the library's API:
        return {
                settings: settings,
                formatMoney: formatMoney,
                formatNumber: formatNumber,
                formatColumn: formatColumn,
                toFixed: toFixed,
                unformat: unformat
        };
  }());
  
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
0.1.3 - formatNumber
accounting.formatNumber(Math.random()*10000);
ready
0.1.3+ (pull request) - formatNumber
accounting_miller.formatNumber(Math.random()*10000);
ready
0.1.3 - formatNumber(array)
accounting.formatNumber([[1234,5678], [1,1234567890]]);
ready
0.1.3+ (pull request) - formatNumber(array)
accounting_miller.formatNumber([[1234,5678], [1,1234567890]]);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.