/*! * numeral.js * version : 1.5.3 * author : Adam Draper * license : MIT * http://adamwdraper.github.com/Numeral-js/ */ var numeralFactory = function () { /************************************ Constants ************************************/ var numeral, VERSION = '1.5.3', // internal storage for language config files languages = {}, currentLanguage = 'en', zeroFormat = null, defaultFormat = '0,0'; /************************************ Constructors ************************************/ // Numeral prototype object function Numeral (number) { this._value = number; } /** * 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, maxDecimals, roundingFunction, optionals) { var splitValue = value.toString().split('.'), minDecimals = maxDecimals - (optionals || 0), boundedPrecision, optionalsRegExp, power, output; // Use the smallest precision value possible to avoid errors from floating point representation if (splitValue.length === 2) { boundedPrecision = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals); } else { boundedPrecision = minDecimals; } power = Math.pow(10, boundedPrecision); // Multiply up by precision, round accurately, then divide and use native toFixed(): output = (roundingFunction(value + 'e+' + boundedPrecision) / power).toFixed(boundedPrecision); if (optionals > maxDecimals - boundedPrecision) { optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$'); output = output.replace(optionalsRegExp, ''); } return output; } /************************************ Formatting ************************************/ // determine what type of formatting we need to do function formatNumeral (n, format, roundingFunction) { var output; // figure out what kind of format we are dealing with if (format.indexOf('$') > -1) { // currency!!!!! output = formatCurrency(n, format, roundingFunction); } else if (format.indexOf('%') > -1) { // percentage output = formatPercentage(n, format, roundingFunction); } else if (format.indexOf(':') > -1) { // time output = formatTime(n, format); } else if (format.indexOf('b') > -1) { // bytes or bits output = formatBytes(n, format, roundingFunction); } else { // plain ol' numbers or bytes output = formatNumber(n._value, format, roundingFunction); } // return string return output; } // revert to number function unformatNumeral (n, string) { var stringOriginal = string, thousandRegExp, millionRegExp, billionRegExp, trillionRegExp, suffixes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], bytesMultiplier = false, power; if (string.indexOf(':') > -1) { n._value = unformatTime(string); } else { if (string === zeroFormat) { n._value = 0; } else { if (languages[currentLanguage].delimiters.decimal !== '.') { string = string.replace(/\./g,'').replace(languages[currentLanguage].delimiters.decimal, '.'); } // see if abbreviations are there so that we can multiply to the correct number thousandRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.thousand + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'); millionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.million + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'); billionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.billion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'); trillionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.trillion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'); // see if bytes are there so that we can multiply to the correct number for (power = 0; power < suffixes.length; ++power) { bytesMultiplier = (string.indexOf(suffixes[power]) > -1) ? Math.pow(1024, power + 1) : false; if (bytesMultiplier) { break; } } // do some math to create our number n._value = ((bytesMultiplier) ? bytesMultiplier : 1) * ((stringOriginal.match(thousandRegExp)) ? Math.pow(10, 3) : 1) * ((stringOriginal.match(millionRegExp)) ? Math.pow(10, 6) : 1) * ((stringOriginal.match(billionRegExp)) ? Math.pow(10, 9) : 1) * ((stringOriginal.match(trillionRegExp)) ? Math.pow(10, 12) : 1) * ((string.indexOf('%') > -1) ? 0.01 : 1) * (((string.split('-').length + Math.min(string.split('(').length-1, string.split(')').length-1)) % 2)? 1: -1) * Number(string.replace(/[^0-9\.]+/g, '')); // round if we are talking about bytes n._value = (bytesMultiplier) ? Math.ceil(n._value) : n._value; } } return n._value; } function formatCurrency (n, format, roundingFunction) { var symbolIndex = format.indexOf('$'), openParenIndex = format.indexOf('('), minusSignIndex = format.indexOf('-'), space = '', spliceIndex, output; // check for space before or after currency if (format.indexOf(' $') > -1) { space = ' '; format = format.replace(' $', ''); } else if (format.indexOf('$ ') > -1) { space = ' '; format = format.replace('$ ', ''); } else { format = format.replace('$', ''); } // format the number output = formatNumber(n._value, format, roundingFunction); // position the symbol if (symbolIndex <= 1) { if (output.indexOf('(') > -1 || output.indexOf('-') > -1) { output = output.split(''); spliceIndex = 1; if (symbolIndex < openParenIndex || symbolIndex < minusSignIndex){ // the symbol appears before the "(" or "-" spliceIndex = 0; } output.splice(spliceIndex, 0, languages[currentLanguage].currency.symbol + space); output = output.join(''); } else { output = languages[currentLanguage].currency.symbol + space + output; } } else { if (output.indexOf(')') > -1) { output = output.split(''); output.splice(-1, 0, space + languages[currentLanguage].currency.symbol); output = output.join(''); } else { output = output + space + languages[currentLanguage].currency.symbol; } } return output; } function formatPercentage (n, format, roundingFunction) { var space = '', output, value = n._value * 100; // check for space before % if (format.indexOf(' %') > -1) { space = ' '; format = format.replace(' %', ''); } else { format = format.replace('%', ''); } output = formatNumber(value, format, roundingFunction); if (output.indexOf(')') > -1 ) { output = output.split(''); output.splice(-1, 0, space + '%'); output = output.join(''); } else { output = output + space + '%'; } return output; } function formatTime (n) { var hours = Math.floor(Math.abs(n._value)/60/60), minutes = Math.floor((Math.abs(n._value) - (hours * 60 * 60))/60), seconds = Math.round(Math.abs(n._value) - (hours * 60 * 60) - (minutes * 60)), direction = n._value < 0 ? '-' : ''; return direction + hours + ':' + ((minutes < 10) ? '0' + minutes : minutes) + ':' + ((seconds < 10) ? '0' + seconds : seconds); } function unformatTime (string) { var timeArray = string.split(':'), seconds = 0; // turn hours and minutes into seconds and add them all up if (timeArray.length === 3) { // hours seconds = seconds + (Number(timeArray[0]) * 60 * 60); // minutes seconds = seconds + (Number(timeArray[1]) * 60); // seconds seconds = seconds + Number(timeArray[2]); } else if (timeArray.length === 2) { // minutes seconds = seconds + (Number(timeArray[0]) * 60); // seconds seconds = seconds + Number(timeArray[1]); } return Number(seconds); } function formatBytes(n, format, roundingFunction) { var value = n._value, units, bytes = '', isBase1000 = false, isStandard = false, isBits = false; // see if we are formatting bytes in decimal or binary if (format.indexOf('bitd') > -1) { // check for space before if (format.indexOf(' bitd') > -1) { bytes = ' '; format = format.replace(' bitd', ''); } else { format = format.replace('bitd', ''); } isBase1000 = true; isStandard = true; isBits = true; } else if (format.indexOf('bitb') > -1) { // check for space before if (format.indexOf(' bitb') > -1) { bytes = ' '; format = format.replace(' bitb', ''); } else { format = format.replace('bitb', ''); } isStandard = true; isBits = true; // see if we are formatting bytes in decimal or binary } else if (format.indexOf('bd') > -1) { // check for space before if (format.indexOf(' bd') > -1) { bytes = ' '; format = format.replace(' bd', ''); } else { format = format.replace('bd', ''); } isBase1000 = true; isStandard = true; } else if (format.indexOf('bb') > -1) { // check for space before if (format.indexOf(' bb') > -1) { bytes = ' '; format = format.replace(' bb', ''); } else { format = format.replace('bb', ''); } isStandard = true; } else if (format.indexOf('b') > -1) { // check for space before if (format.indexOf(' b') > -1) { bytes = ' '; format = format.replace(' b', ''); } else { format = format.replace('b', ''); } } units = getByteSuffix( value, isBase1000, // use base 1000 isStandard, // use standard decimal suffixes isBits // use bit format ); value = units.value; bytes = bytes + units.suffix; return formatNumber(value, format, roundingFunction) + bytes; } function getByteSuffix (value, isDecimal, useStandardSuffix, isBits) { var base = !!isDecimal ? 1000 : 1024, suffixes = !!isBits ? isDecimal ? ['bit', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'] : ['bit', 'Kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'] : (!isDecimal && useStandardSuffix) ? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] : ['B', isDecimal ? 'kB' : 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], suffix = suffixes[0], power, min, max, abs = Math.abs(value), matched = (abs < base); if (!matched) { for (power = 1; power < suffixes.length; ++power) { min = Math.pow(base, power); max = Math.pow(base, power + 1); if (abs >= min && abs < max) { matched = true; suffix = suffixes[power]; value = value / min; break; } } // values greater than or equal to 1000/1024 YB if (!matched) { value = value / Math.pow(base, suffixes.length - 1); suffix = suffixes[suffixes.length - 1]; } } return { value: value, suffix: suffix }; } function formatNumber (value, format, roundingFunction) { var negP = false, signed = false, optDec = false, abbr = '', abbrK = false, // force abbreviation to thousands abbrM = false, // force abbreviation to millions abbrB = false, // force abbreviation to billions abbrT = false, // force abbreviation to trillions abbrForce = false, // force abbreviation ord = '', abs = Math.abs(value), w, precision, thousands, d = '', neg = false; // check if number is zero and a custom zero format has been set if (value === 0 && zeroFormat !== null) { return zeroFormat; } else { var isExponent = 'number' === typeof value && value.toString().match(/e[+-]/); // see if we should use parentheses for negative number or if we should prefix with a sign // if both are present we default to parentheses if (format.indexOf('(') > -1) { negP = true; format = format.slice(1, -1); } else if (format.indexOf('+') > -1) { signed = true; format = format.replace(/\+/g, ''); } // see if abbreviation is wanted if (format.indexOf('a') > -1) { // check if abbreviation is specified abbrK = format.indexOf('aK') >= 0; abbrM = format.indexOf('aM') >= 0; abbrB = format.indexOf('aB') >= 0; abbrT = format.indexOf('aT') >= 0; abbrForce = abbrK || abbrM || abbrB || abbrT; // check for space before abbreviation if (format.indexOf(' a') > -1) { abbr = ' '; format = format.replace(' a', ''); } else { format = format.replace('a', ''); } if (abs >= Math.pow(10, 12) && !abbrForce || abbrT) { // trillion abbr = abbr + languages[currentLanguage].abbreviations.trillion; value = value / Math.pow(10, 12); } else if (abs < Math.pow(10, 12) && abs >= Math.pow(10, 9) && !abbrForce || abbrB) { // billion abbr = abbr + languages[currentLanguage].abbreviations.billion; value = value / Math.pow(10, 9); } else if (abs < Math.pow(10, 9) && abs >= Math.pow(10, 6) && !abbrForce || abbrM) { // million abbr = abbr + languages[currentLanguage].abbreviations.million; value = value / Math.pow(10, 6); } else if (abs < Math.pow(10, 6) && abs >= Math.pow(10, 3) && !abbrForce || abbrK) { // thousand abbr = abbr + languages[currentLanguage].abbreviations.thousand; value = value / Math.pow(10, 3); } } // see if ordinal is wanted if (format.indexOf('o') > -1) { // check for space before if (format.indexOf(' o') > -1) { ord = ' '; format = format.replace(' o', ''); } else { format = format.replace('o', ''); } ord = ord + languages[currentLanguage].ordinal(value); } if (format.indexOf('[.]') > -1) { optDec = true; format = format.replace('[.]', '.'); } w = value.toString().split('.')[0]; precision = format.split('.')[1]; thousands = format.indexOf(','); if (precision && !isExponent) { if (precision.indexOf('[') > -1) { precision = precision.replace(']', ''); precision = precision.split('['); d = toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length); } else { d = toFixed(value, precision.length, roundingFunction); } w = d.split('.')[0]; if (d.indexOf('.') != -1 && d.split('.')[1].length) { d = languages[currentLanguage].delimiters.decimal + d.split('.')[1]; } else { d = ''; } if (optDec && Number(d.slice(1)) === 0) { d = ''; } } else { // don't case exponents to a fixed value, just cast them to strings if (isExponent) w = value.toString(); else w = toFixed(value, 0, roundingFunction); } // format negative number if (w.indexOf('-') === 0) { w = w.slice(1); neg = true; } if (thousands > -1) { w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + languages[currentLanguage].delimiters.thousands); } if (format.indexOf('.') === 0) { w = ''; } return ((negP && neg) ? '(' : '') + ((!negP && neg) ? '-' : '') + ((!neg && signed) ? '+' : '') + w + d + ((ord) ? ord : '') + ((abbr) ? abbr : '') + ((negP && neg) ? ')' : ''); } } /************************************ Top Level Functions ************************************/ numeral = function (input) { if (numeral.isNumeral(input)) { input = input.value(); } else if (input === 0 || typeof input === 'undefined') { input = 0; } else if (!Number(input)) { input = numeral.fn.unformat(input); } return new Numeral(Number(input)); }; // version number numeral.version = VERSION; // compare numeral object numeral.isNumeral = function (obj) { return obj instanceof Numeral; }; // This function will load languages and then set the global language. If // no arguments are passed in, it will simply return the current global // language key. numeral.language = function (key, values) { if (!key) { return currentLanguage; } if (key && !values) { if(!languages[key]) { throw new Error('Unknown language : ' + key); } currentLanguage = key; } if (values || !languages[key]) { loadLanguage(key, values); } return numeral; }; // This function provides access to the loaded language data. If // no arguments are passed in, it will simply return the current // global language object. numeral.languageData = function (key) { if (!key) { return languages[currentLanguage]; } if (!languages[key]) { throw new Error('Unknown language : ' + key); } return languages[key]; }; numeral.language('en', { delimiters: { thousands: ',', decimal: '.' }, abbreviations: { thousand: 'k', million: 'm', billion: 'b', trillion: 't' }, ordinal: function (number) { var b = number % 10; return (~~ (number % 100 / 10) === 1) ? 'th' : (b === 1) ? 'st' : (b === 2) ? 'nd' : (b === 3) ? 'rd' : 'th'; }, currency: { symbol: '$' } }); numeral.zeroFormat = function (format) { zeroFormat = typeof(format) === 'string' ? format : null; }; numeral.defaultFormat = function (format) { defaultFormat = typeof(format) === 'string' ? format : '0.0'; }; /************************************ Helpers ************************************/ function loadLanguage(key, values) { languages[key] = values; } /************************************ Floating-point helpers ************************************/ // The floating-point helper functions and implementation // borrows heavily from sinful.js: http://guipn.github.io/sinful.js/ /** * Array.prototype.reduce for browsers that don't support it * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Compatibility */ if ('function' !== typeof Array.prototype.reduce) { Array.prototype.reduce = function (callback, opt_initialValue) { 'use strict'; if (null === this || 'undefined' === typeof this) { // At the moment all modern browsers, that support strict mode, have // native implementation of Array.prototype.reduce. For instance, IE8 // does not support strict mode, so this check is actually useless. throw new TypeError('Array.prototype.reduce called on null or undefined'); } if ('function' !== typeof callback) { throw new TypeError(callback + ' is not a function'); } var index, value, length = this.length >>> 0, isValueSet = false; if (1 < arguments.length) { value = opt_initialValue; isValueSet = true; } for (index = 0; length > index; ++index) { if (this.hasOwnProperty(index)) { if (isValueSet) { value = callback(value, this[index], index, this); } else { value = this[index]; isValueSet = true; } } } if (!isValueSet) { throw new TypeError('Reduce of empty array with no initial value'); } return value; }; } /** * Computes the multiplier necessary to make x >= 1, * effectively eliminating miscalculations caused by * finite precision. */ function multiplier(x) { var parts = x.toString().split('.'); if (parts.length < 2) { return 1; } return Math.pow(10, parts[1].length); } /** * Given a variable number of arguments, returns the maximum * multiplier that must be used to normalize an operation involving * all of them. */ function correctionFactor() { var args = Array.prototype.slice.call(arguments); return args.reduce(function (prev, next) { var mp = multiplier(prev), mn = multiplier(next); return mp > mn ? mp : mn; }, -Infinity); } /************************************ Numeral Prototype ************************************/ numeral.fn = Numeral.prototype = { clone : function () { return numeral(this); }, format : function (inputString, roundingFunction) { return formatNumeral(this, inputString ? inputString : defaultFormat, (roundingFunction !== undefined) ? roundingFunction : Math.round ); }, unformat : function (inputString) { if (Object.prototype.toString.call(inputString) === '[object Number]') { return inputString; } return unformatNumeral(this, inputString ? inputString : defaultFormat); }, byteUnits : function (isDecimal, useStandardSuffix, isBits) { return getByteSuffix(this._value, isDecimal, useStandardSuffix, isBits).suffix; }, value : function () { return this._value; }, valueOf : function () { return this._value; }, set : function (value) { this._value = Number(value); return this; }, add : function (value) { var corrFactor = correctionFactor.call(null, this._value, value); function cback(accum, curr, currI, O) { return accum + corrFactor * curr; } this._value = [this._value, value].reduce(cback, 0) / corrFactor; return this; }, subtract : function (value) { var corrFactor = correctionFactor.call(null, this._value, value); function cback(accum, curr, currI, O) { return accum - corrFactor * curr; } this._value = [value].reduce(cback, this._value * corrFactor) / corrFactor; return this; }, multiply : function (value) { function cback(accum, curr, currI, O) { var corrFactor = correctionFactor(accum, curr); return (accum * corrFactor) * (curr * corrFactor) / (corrFactor * corrFactor); } this._value = [this._value, value].reduce(cback, 1); return this; }, divide : function (value) { function cback(accum, curr, currI, O) { var corrFactor = correctionFactor(accum, curr); return (accum * corrFactor) / (curr * corrFactor); } this._value = [this._value, value].reduce(cback); return this; }, difference : function (value) { return Math.abs(numeral(this._value).subtract(value).value()); } }; return numeral; }; // expose numeral via UMD wrapper (function (root, factory) { if (typeof define === "function" && define.amd) { define([], factory); } else if (typeof exports === "object") { module.exports = factory(); } else { root.numeral = factory(); } }(this, numeralFactory));