You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1293 lines
43 KiB
1293 lines
43 KiB
/*!
|
|
* numbro.js
|
|
* version : 1.11.0
|
|
* author : Företagsplatsen AB
|
|
* license : MIT
|
|
* http://www.foretagsplatsen.se
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
/************************************
|
|
Constants
|
|
************************************/
|
|
|
|
var numbro,
|
|
VERSION = '1.11.0',
|
|
binarySuffixes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'],
|
|
decimalSuffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
|
|
bytes = {
|
|
general: { scale: 1024, suffixes: decimalSuffixes, marker: 'bd' },
|
|
binary: { scale: 1024, suffixes: binarySuffixes, marker: 'b' },
|
|
decimal: { scale: 1000, suffixes: decimalSuffixes, marker: 'd' }
|
|
},
|
|
// general must be before the others because it reuses their characters!
|
|
byteFormatOrder = [ bytes.general, bytes.binary, bytes.decimal ],
|
|
// internal storage for culture config files
|
|
cultures = {},
|
|
// Todo: Remove in 2.0.0
|
|
languages = cultures,
|
|
currentCulture = 'en-US',
|
|
zeroFormat = null,
|
|
defaultFormat = '0,0',
|
|
defaultCurrencyFormat = '0$',
|
|
// check for nodeJS
|
|
hasModule = (typeof module !== 'undefined' && module.exports),
|
|
// default culture
|
|
enUS = {
|
|
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: '$',
|
|
position: 'prefix'
|
|
},
|
|
defaults: {
|
|
currencyFormat: ',0000 a'
|
|
},
|
|
formats: {
|
|
fourDigits: '0000 a',
|
|
fullWithTwoDecimals: '$ ,0.00',
|
|
fullWithTwoDecimalsNoCurrency: ',0.00'
|
|
}
|
|
};
|
|
|
|
/************************************
|
|
Constructors
|
|
************************************/
|
|
|
|
|
|
// Numbro prototype object
|
|
function Numbro(number) {
|
|
this._value = number;
|
|
}
|
|
|
|
function numberLength(number) {
|
|
if (number === 0) { return 1; }
|
|
return Math.floor(Math.log(Math.abs(number)) / Math.LN10) + 1;
|
|
}
|
|
|
|
function zeroes(count) {
|
|
var i, ret = '';
|
|
|
|
for (i = 0; i < count; i++) {
|
|
ret += '0';
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/**
|
|
* Implementation of toFixed() for numbers with exponents
|
|
* This function may return negative representations for zero values e.g. "-0.0"
|
|
*/
|
|
function toFixedLargeSmall(value, precision) {
|
|
var mantissa,
|
|
beforeDec,
|
|
afterDec,
|
|
exponent,
|
|
prefix,
|
|
endStr,
|
|
zerosStr,
|
|
str;
|
|
|
|
str = value.toString();
|
|
|
|
mantissa = str.split('e')[0];
|
|
exponent = str.split('e')[1];
|
|
|
|
beforeDec = mantissa.split('.')[0];
|
|
afterDec = mantissa.split('.')[1] || '';
|
|
|
|
if (+exponent > 0) {
|
|
// exponent is positive - add zeros after the numbers
|
|
str = beforeDec + afterDec + zeroes(exponent - afterDec.length);
|
|
} else {
|
|
// exponent is negative
|
|
|
|
if (+beforeDec < 0) {
|
|
prefix = '-0';
|
|
} else {
|
|
prefix = '0';
|
|
}
|
|
|
|
// tack on the decimal point if needed
|
|
if (precision > 0) {
|
|
prefix += '.';
|
|
}
|
|
|
|
zerosStr = zeroes((-1 * exponent) - 1);
|
|
// substring off the end to satisfy the precision
|
|
endStr = (zerosStr + Math.abs(beforeDec) + afterDec).substr(0, precision);
|
|
str = prefix + endStr;
|
|
}
|
|
|
|
// only add percision 0's if the exponent is positive
|
|
if (+exponent > 0 && precision > 0) {
|
|
str += '.' + zeroes(precision);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Also removes negative signs for zero-formatted numbers. e.g. -0.01 w/ precision 1 -> 0.0
|
|
*/
|
|
function toFixed(value, precision, roundingFunction, optionals) {
|
|
var power = Math.pow(10, precision),
|
|
optionalsRegExp,
|
|
output;
|
|
|
|
if (value.toString().indexOf('e') > -1) {
|
|
// toFixed returns scientific notation for numbers above 1e21 and below 1e-7
|
|
output = toFixedLargeSmall(value, precision);
|
|
// remove the leading negative sign if it exists and should not be present (e.g. -0.00)
|
|
if (output.charAt(0) === '-' && +output >= 0) {
|
|
output = output.substr(1); // chop off the '-'
|
|
}
|
|
}
|
|
else {
|
|
// Multiply up by precision, round accurately, then divide and use native toFixed():
|
|
output = (roundingFunction(value + 'e+' + precision) / power).toFixed(precision);
|
|
}
|
|
|
|
if (optionals) {
|
|
optionalsRegExp = new RegExp('0{1,' + optionals + '}$');
|
|
output = output.replace(optionalsRegExp, '');
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/************************************
|
|
Formatting
|
|
************************************/
|
|
|
|
// determine what type of formatting we need to do
|
|
function formatNumbro(n, format, roundingFunction) {
|
|
var output,
|
|
escapedFormat = format.replace(/\{[^\{\}]*\}/g, '');
|
|
|
|
// figure out what kind of format we are dealing with
|
|
if (escapedFormat.indexOf('$') > -1) { // currency!!!!!
|
|
output = formatCurrency(n, cultures[currentCulture].currency.symbol, format, roundingFunction);
|
|
} else if (escapedFormat.indexOf('%') > -1) { // percentage
|
|
output = formatPercentage(n, format, roundingFunction);
|
|
} else if (escapedFormat.indexOf(':') > -1) { // time
|
|
output = formatTime(n, format);
|
|
} else { // plain ol' numbers or bytes
|
|
output = formatNumber(n._value, format, roundingFunction);
|
|
}
|
|
|
|
// return string
|
|
return output;
|
|
}
|
|
|
|
// revert to number
|
|
function unformatNumbro(n, string) {
|
|
var stringOriginal = string,
|
|
thousandRegExp,
|
|
millionRegExp,
|
|
billionRegExp,
|
|
trillionRegExp,
|
|
bytesMultiplier = false,
|
|
power;
|
|
|
|
if (string.indexOf(':') > -1) {
|
|
n._value = unformatTime(string);
|
|
} else {
|
|
if (string === zeroFormat) {
|
|
n._value = 0;
|
|
} else {
|
|
if (cultures[currentCulture].delimiters.decimal !== '.') {
|
|
string = string.replace(/\./g, '').replace(cultures[currentCulture].delimiters.decimal, '.');
|
|
}
|
|
|
|
// see if abbreviations are there so that we can multiply to the correct number
|
|
thousandRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.thousand +
|
|
'(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
|
|
millionRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.million +
|
|
'(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
|
|
billionRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.billion +
|
|
'(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
|
|
trillionRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.trillion +
|
|
'(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
|
|
|
|
// see if bytes are there so that we can multiply to the correct number
|
|
for (power = 1; power < binarySuffixes.length && !bytesMultiplier; ++power) {
|
|
if (string.indexOf(binarySuffixes[power]) > -1) {
|
|
bytesMultiplier = Math.pow(1024, power);
|
|
} else if (string.indexOf(decimalSuffixes[power]) > -1) {
|
|
bytesMultiplier = Math.pow(1000, power);
|
|
}
|
|
}
|
|
|
|
var str = string.replace(/[^0-9\.]+/g, '');
|
|
if (str === '') {
|
|
// An empty string is not a number.
|
|
n._value = NaN;
|
|
|
|
} else {
|
|
// 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(str);
|
|
|
|
// round if we are talking about bytes
|
|
n._value = (bytesMultiplier) ? Math.ceil(n._value) : n._value;
|
|
}
|
|
}
|
|
}
|
|
return n._value;
|
|
}
|
|
|
|
function formatCurrency(n, currencySymbol, originalFormat, roundingFunction) {
|
|
var format = originalFormat,
|
|
symbolIndex = format.indexOf('$'),
|
|
openParenIndex = format.indexOf('('),
|
|
plusSignIndex = format.indexOf('+'),
|
|
minusSignIndex = format.indexOf('-'),
|
|
space = '',
|
|
decimalSeparator = '',
|
|
spliceIndex,
|
|
output;
|
|
|
|
if(format.indexOf('$') === -1){
|
|
// Use defaults instead of the format provided
|
|
if (cultures[currentCulture].currency.position === 'infix') {
|
|
decimalSeparator = currencySymbol;
|
|
if (cultures[currentCulture].currency.spaceSeparated) {
|
|
decimalSeparator = ' ' + decimalSeparator + ' ';
|
|
}
|
|
} else if (cultures[currentCulture].currency.spaceSeparated) {
|
|
space = ' ';
|
|
}
|
|
} else {
|
|
// 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, decimalSeparator);
|
|
|
|
if (originalFormat.indexOf('$') === -1) {
|
|
// Use defaults instead of the format provided
|
|
switch (cultures[currentCulture].currency.position) {
|
|
case 'postfix':
|
|
if (output.indexOf(')') > -1) {
|
|
output = output.split('');
|
|
output.splice(-1, 0, space + currencySymbol);
|
|
output = output.join('');
|
|
} else {
|
|
output = output + space + currencySymbol;
|
|
}
|
|
break;
|
|
case 'infix':
|
|
break;
|
|
case 'prefix':
|
|
if (output.indexOf('(') > -1 || output.indexOf('-') > -1) {
|
|
output = output.split('');
|
|
spliceIndex = Math.max(openParenIndex, minusSignIndex) + 1;
|
|
|
|
output.splice(spliceIndex, 0, currencySymbol + space);
|
|
output = output.join('');
|
|
} else {
|
|
output = currencySymbol + space + output;
|
|
}
|
|
break;
|
|
default:
|
|
throw Error('Currency position should be among ["prefix", "infix", "postfix"]');
|
|
}
|
|
} else {
|
|
// position the symbol
|
|
if (symbolIndex <= 1) {
|
|
if (output.indexOf('(') > -1 || output.indexOf('+') > -1 || output.indexOf('-') > -1) {
|
|
output = output.split('');
|
|
spliceIndex = 1;
|
|
if (symbolIndex < openParenIndex || symbolIndex < plusSignIndex || symbolIndex < minusSignIndex) {
|
|
// the symbol appears before the "(", "+" or "-"
|
|
spliceIndex = 0;
|
|
}
|
|
output.splice(spliceIndex, 0, currencySymbol + space);
|
|
output = output.join('');
|
|
} else {
|
|
output = currencySymbol + space + output;
|
|
}
|
|
} else {
|
|
if (output.indexOf(')') > -1) {
|
|
output = output.split('');
|
|
output.splice(-1, 0, space + currencySymbol);
|
|
output = output.join('');
|
|
} else {
|
|
output = output + space + currencySymbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function formatForeignCurrency(n, foreignCurrencySymbol, originalFormat, roundingFunction) {
|
|
return formatCurrency(n, foreignCurrencySymbol, originalFormat, roundingFunction);
|
|
}
|
|
|
|
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(n._value / 60 / 60),
|
|
minutes = Math.floor((n._value - (hours * 60 * 60)) / 60),
|
|
seconds = Math.round(n._value - (hours * 60 * 60) - (minutes * 60));
|
|
return 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 formatByteUnits (value, suffixes, scale) {
|
|
var suffix = suffixes[0],
|
|
power,
|
|
min,
|
|
max,
|
|
abs = Math.abs(value);
|
|
|
|
if (abs >= scale) {
|
|
for (power = 1; power < suffixes.length; ++power) {
|
|
min = Math.pow(scale, power);
|
|
max = Math.pow(scale, power + 1);
|
|
|
|
if (abs >= min && abs < max) {
|
|
suffix = suffixes[power];
|
|
value = value / min;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// values greater than or equal to [scale] YB never set the suffix
|
|
if (suffix === suffixes[0]) {
|
|
value = value / Math.pow(scale, suffixes.length - 1);
|
|
suffix = suffixes[suffixes.length - 1];
|
|
}
|
|
}
|
|
|
|
return { value: value, suffix: suffix };
|
|
}
|
|
|
|
function formatNumber (value, format, roundingFunction, sep) {
|
|
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
|
|
bytes = '',
|
|
byteFormat,
|
|
units,
|
|
ord = '',
|
|
abs = Math.abs(value),
|
|
totalLength,
|
|
length,
|
|
minimumPrecision,
|
|
pow,
|
|
w,
|
|
intPrecision,
|
|
precision,
|
|
prefix,
|
|
postfix,
|
|
thousands,
|
|
d = '',
|
|
forcedNeg = false,
|
|
neg = false,
|
|
indexOpenP,
|
|
indexMinus,
|
|
paren = '',
|
|
minlen,
|
|
i;
|
|
|
|
// check if number is zero and a custom zero format has been set
|
|
if (value === 0 && zeroFormat !== null) {
|
|
return zeroFormat;
|
|
}
|
|
|
|
if (!isFinite(value)) {
|
|
return '' + value;
|
|
}
|
|
|
|
if (format.indexOf('{') === 0) {
|
|
var end = format.indexOf('}');
|
|
if (end === -1) {
|
|
throw Error('Format should also contain a "}"');
|
|
}
|
|
prefix = format.slice(1, end);
|
|
format = format.slice(end + 1);
|
|
} else {
|
|
prefix = '';
|
|
}
|
|
|
|
if (format.indexOf('}') === format.length - 1 && format.length) {
|
|
var start = format.indexOf('{');
|
|
if (start === -1) {
|
|
throw Error('Format should also contain a "{"');
|
|
}
|
|
postfix = format.slice(start + 1, -1);
|
|
format = format.slice(0, start + 1);
|
|
} else {
|
|
postfix = '';
|
|
}
|
|
|
|
// check for min length
|
|
var info;
|
|
if (format.indexOf('.') === -1) {
|
|
info = format.match(/([0-9]+).*/);
|
|
} else {
|
|
info = format.match(/([0-9]+)\..*/);
|
|
}
|
|
minlen = info === null ? -1 : info[1].length;
|
|
|
|
// 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) {
|
|
forcedNeg = true;
|
|
}
|
|
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) {
|
|
intPrecision = format.split('.')[0].match(/[0-9]+/g) || ['0'];
|
|
intPrecision = parseInt(intPrecision[0], 10);
|
|
|
|
// 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', '');
|
|
}
|
|
|
|
totalLength = numberLength(value);
|
|
minimumPrecision = totalLength % 3;
|
|
minimumPrecision = minimumPrecision === 0 ? 3 : minimumPrecision;
|
|
|
|
if (intPrecision && abs !== 0) {
|
|
pow = 3 * ~~((Math.min(intPrecision, totalLength) - minimumPrecision) / 3);
|
|
abs = abs / Math.pow(10, pow);
|
|
}
|
|
|
|
if (totalLength !== intPrecision) {
|
|
if (abs >= Math.pow(10, 12) && !abbrForce || abbrT) {
|
|
// trillion
|
|
abbr = abbr + cultures[currentCulture].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 + cultures[currentCulture].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 + cultures[currentCulture].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 + cultures[currentCulture].abbreviations.thousand;
|
|
value = value / Math.pow(10, 3);
|
|
}
|
|
}
|
|
|
|
length = numberLength(value);
|
|
if (intPrecision && length < intPrecision && format.indexOf('.') === -1) {
|
|
format += '[.]';
|
|
format += zeroes(intPrecision - length);
|
|
}
|
|
}
|
|
|
|
// see if we are formatting
|
|
// binary-decimal bytes (1024 MB), binary bytes (1024 MiB), or decimal bytes (1000 MB)
|
|
for (i = 0; i < byteFormatOrder.length; ++i) {
|
|
byteFormat = byteFormatOrder[i];
|
|
|
|
if (format.indexOf(byteFormat.marker) > -1) {
|
|
// check for space before
|
|
if (format.indexOf(' ' + byteFormat.marker) >-1) {
|
|
bytes = ' ';
|
|
}
|
|
|
|
// remove the marker (with the space if it had one)
|
|
format = format.replace(bytes + byteFormat.marker, '');
|
|
|
|
units = formatByteUnits(value, byteFormat.suffixes, byteFormat.scale);
|
|
|
|
value = units.value;
|
|
bytes = bytes + units.suffix;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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', '');
|
|
}
|
|
|
|
if (cultures[currentCulture].ordinal) {
|
|
ord = ord + cultures[currentCulture].ordinal(value);
|
|
}
|
|
}
|
|
|
|
if (format.indexOf('[.]') > -1) {
|
|
optDec = true;
|
|
format = format.replace('[.]', '.');
|
|
}
|
|
|
|
precision = format.split('.')[1];
|
|
thousands = format.indexOf(',');
|
|
|
|
if (precision) {
|
|
var dSplit = [];
|
|
|
|
if (precision.indexOf('*') !== -1) {
|
|
d = value.toString();
|
|
dSplit = d.split('.');
|
|
if (dSplit.length > 1) {
|
|
d = toFixed(value, dSplit[1].length, roundingFunction);
|
|
}
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
|
|
dSplit = d.split('.');
|
|
w = dSplit[0];
|
|
|
|
if (dSplit.length > 1 && dSplit[1].length) {
|
|
var p = sep ? abbr + sep : cultures[currentCulture].delimiters.decimal;
|
|
d = p + dSplit[1];
|
|
} else {
|
|
d = '';
|
|
}
|
|
|
|
if (optDec && Number(d.slice(1)) === 0) {
|
|
d = '';
|
|
}
|
|
} else {
|
|
w = toFixed(value, 0, roundingFunction);
|
|
}
|
|
|
|
// format number
|
|
if (w.indexOf('-') > -1) {
|
|
w = w.slice(1);
|
|
neg = true;
|
|
}
|
|
|
|
if (w.length < minlen) {
|
|
w = zeroes(minlen - w.length) + w;
|
|
}
|
|
|
|
if (thousands > -1) {
|
|
w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' +
|
|
cultures[currentCulture].delimiters.thousands);
|
|
}
|
|
|
|
if (format.indexOf('.') === 0) {
|
|
w = '';
|
|
}
|
|
|
|
indexOpenP = format.indexOf('(');
|
|
indexMinus = format.indexOf('-');
|
|
|
|
if (indexOpenP < indexMinus) {
|
|
paren = ((negP && neg) ? '(' : '') + (((forcedNeg && neg) || (!negP && neg)) ? '-' : '');
|
|
} else {
|
|
paren = (((forcedNeg && neg) || (!negP && neg)) ? '-' : '') + ((negP && neg) ? '(' : '');
|
|
}
|
|
|
|
return prefix +
|
|
paren + ((!neg && signed && value !== 0) ? '+' : '') +
|
|
w + d +
|
|
((ord) ? ord : '') +
|
|
((abbr && !sep) ? abbr : '') +
|
|
((bytes) ? bytes : '') +
|
|
((negP && neg) ? ')' : '') +
|
|
postfix;
|
|
}
|
|
|
|
/************************************
|
|
Top Level Functions
|
|
************************************/
|
|
|
|
numbro = function(input) {
|
|
if (numbro.isNumbro(input)) {
|
|
input = input.value();
|
|
} else if (typeof input === 'string' || typeof input === 'number') {
|
|
input = numbro.fn.unformat(input);
|
|
} else {
|
|
input = NaN;
|
|
}
|
|
|
|
return new Numbro(Number(input));
|
|
};
|
|
|
|
// version number
|
|
numbro.version = VERSION;
|
|
|
|
// compare numbro object
|
|
numbro.isNumbro = function(obj) {
|
|
return obj instanceof Numbro;
|
|
};
|
|
|
|
/**
|
|
* This function allow the user to set a new language with a fallback if
|
|
* the language does not exist. If no fallback language is provided,
|
|
* it fallbacks to english.
|
|
*
|
|
* @deprecated Since in version 1.6.0. It will be deleted in version 2.0
|
|
* `setCulture` should be used instead.
|
|
*/
|
|
numbro.setLanguage = function(newLanguage, fallbackLanguage) {
|
|
console.warn('`setLanguage` is deprecated since version 1.6.0. Use `setCulture` instead');
|
|
var key = newLanguage,
|
|
prefix = newLanguage.split('-')[0],
|
|
matchingLanguage = null;
|
|
if (!languages[key]) {
|
|
Object.keys(languages).forEach(function(language) {
|
|
if (!matchingLanguage && language.split('-')[0] === prefix) {
|
|
matchingLanguage = language;
|
|
}
|
|
});
|
|
key = matchingLanguage || fallbackLanguage || 'en-US';
|
|
}
|
|
chooseCulture(key);
|
|
};
|
|
|
|
/**
|
|
* This function allow the user to set a new culture with a fallback if
|
|
* the culture does not exist. If no fallback culture is provided,
|
|
* it falls back to "en-US".
|
|
*/
|
|
numbro.setCulture = function(newCulture, fallbackCulture) {
|
|
var key = newCulture,
|
|
suffix = newCulture.split('-')[1],
|
|
matchingCulture = null;
|
|
if (!cultures[key]) {
|
|
if (suffix) {
|
|
Object.keys(cultures).forEach(function(language) {
|
|
if (!matchingCulture && language.split('-')[1] === suffix) {
|
|
matchingCulture = language;
|
|
}
|
|
});
|
|
}
|
|
|
|
key = matchingCulture || fallbackCulture || 'en-US';
|
|
}
|
|
chooseCulture(key);
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @deprecated Since in version 1.6.0. It will be deleted in version 2.0
|
|
* `culture` should be used instead.
|
|
*/
|
|
numbro.language = function(key, values) {
|
|
console.warn('`language` is deprecated since version 1.6.0. Use `culture` instead');
|
|
|
|
if (!key) {
|
|
return currentCulture;
|
|
}
|
|
|
|
if (key && !values) {
|
|
if (!languages[key]) {
|
|
throw new Error('Unknown language : ' + key);
|
|
}
|
|
chooseCulture(key);
|
|
}
|
|
|
|
if (values || !languages[key]) {
|
|
setCulture(key, values);
|
|
}
|
|
|
|
return numbro;
|
|
};
|
|
|
|
/**
|
|
* This function will load cultures and then set the global culture. If
|
|
* no arguments are passed in, it will simply return the current global
|
|
* culture code.
|
|
*/
|
|
numbro.culture = function(code, values) {
|
|
if (!code) {
|
|
return currentCulture;
|
|
}
|
|
|
|
if (code && !values) {
|
|
if (!cultures[code]) {
|
|
throw new Error('Unknown culture : ' + code);
|
|
}
|
|
chooseCulture(code);
|
|
}
|
|
|
|
if (values || !cultures[code]) {
|
|
setCulture(code, values);
|
|
}
|
|
|
|
return numbro;
|
|
};
|
|
|
|
/**
|
|
* This function provides access to the loaded language data. If
|
|
* no arguments are passed in, it will simply return the current
|
|
* global language object.
|
|
*
|
|
* @deprecated Since in version 1.6.0. It will be deleted in version 2.0
|
|
* `culture` should be used instead.
|
|
*/
|
|
numbro.languageData = function(key) {
|
|
console.warn('`languageData` is deprecated since version 1.6.0. Use `cultureData` instead');
|
|
|
|
if (!key) {
|
|
return languages[currentCulture];
|
|
}
|
|
|
|
if (!languages[key]) {
|
|
throw new Error('Unknown language : ' + key);
|
|
}
|
|
|
|
return languages[key];
|
|
};
|
|
|
|
/**
|
|
* This function provides access to the loaded culture data. If
|
|
* no arguments are passed in, it will simply return the current
|
|
* global culture object.
|
|
*/
|
|
numbro.cultureData = function(code) {
|
|
if (!code) {
|
|
return cultures[currentCulture];
|
|
}
|
|
|
|
if (!cultures[code]) {
|
|
throw new Error('Unknown culture : ' + code);
|
|
}
|
|
|
|
return cultures[code];
|
|
};
|
|
|
|
numbro.culture('en-US', enUS);
|
|
|
|
/**
|
|
* @deprecated Since in version 1.6.0. It will be deleted in version 2.0
|
|
* `cultures` should be used instead.
|
|
*/
|
|
numbro.languages = function() {
|
|
console.warn('`languages` is deprecated since version 1.6.0. Use `cultures` instead');
|
|
|
|
return languages;
|
|
};
|
|
|
|
numbro.cultures = function() {
|
|
return cultures;
|
|
};
|
|
|
|
numbro.zeroFormat = function(format) {
|
|
zeroFormat = typeof(format) === 'string' ? format : null;
|
|
};
|
|
|
|
numbro.defaultFormat = function(format) {
|
|
defaultFormat = typeof(format) === 'string' ? format : '0.0';
|
|
};
|
|
|
|
numbro.defaultCurrencyFormat = function (format) {
|
|
defaultCurrencyFormat = typeof(format) === 'string' ? format : '0$';
|
|
};
|
|
|
|
numbro.validate = function(val, culture) {
|
|
|
|
var _decimalSep,
|
|
_thousandSep,
|
|
_currSymbol,
|
|
_valArray,
|
|
_abbrObj,
|
|
_thousandRegEx,
|
|
cultureData,
|
|
temp;
|
|
|
|
//coerce val to string
|
|
if (typeof val !== 'string') {
|
|
val += '';
|
|
if (console.warn) {
|
|
console.warn('Numbro.js: Value is not string. It has been co-erced to: ', val);
|
|
}
|
|
}
|
|
|
|
//trim whitespaces from either sides
|
|
val = val.trim();
|
|
|
|
//replace the initial '+' or '-' sign if present
|
|
val = val.replace(/^[+-]?/, '');
|
|
|
|
//if val is just digits return true
|
|
if ( !! val.match(/^\d+$/)) {
|
|
return true;
|
|
}
|
|
|
|
//if val is empty return false
|
|
if (val === '') {
|
|
return false;
|
|
}
|
|
|
|
//get the decimal and thousands separator from numbro.cultureData
|
|
try {
|
|
//check if the culture is understood by numbro. if not, default it to current culture
|
|
cultureData = numbro.cultureData(culture);
|
|
} catch (e) {
|
|
cultureData = numbro.cultureData(numbro.culture());
|
|
}
|
|
|
|
//setup the delimiters and currency symbol based on culture
|
|
_currSymbol = cultureData.currency.symbol;
|
|
_abbrObj = cultureData.abbreviations;
|
|
_decimalSep = cultureData.delimiters.decimal;
|
|
if (cultureData.delimiters.thousands === '.') {
|
|
_thousandSep = '\\.';
|
|
} else {
|
|
_thousandSep = cultureData.delimiters.thousands;
|
|
}
|
|
|
|
// validating currency symbol
|
|
temp = val.match(/^[^\d\.\,]+/);
|
|
if (temp !== null) {
|
|
val = val.substr(1);
|
|
if (temp[0] !== _currSymbol) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//validating abbreviation symbol
|
|
temp = val.match(/[^\d]+$/);
|
|
if (temp !== null) {
|
|
val = val.slice(0, -1);
|
|
if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million &&
|
|
temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
_thousandRegEx = new RegExp(_thousandSep + '{2}');
|
|
|
|
if (!val.match(/[^\d.,]/g)) {
|
|
_valArray = val.split(_decimalSep);
|
|
if (_valArray.length > 2) {
|
|
return false;
|
|
} else {
|
|
if (_valArray.length < 2) {
|
|
return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx));
|
|
} else {
|
|
if (_valArray[0] === '') {
|
|
// for values without leading zero eg. .984
|
|
return (!_valArray[0].match(_thousandRegEx) &&
|
|
!!_valArray[1].match(/^\d+$/));
|
|
|
|
} else if (_valArray[0].length === 1) {
|
|
return ( !! _valArray[0].match(/^\d+$/) &&
|
|
!_valArray[0].match(_thousandRegEx) &&
|
|
!! _valArray[1].match(/^\d+$/));
|
|
} else {
|
|
return ( !! _valArray[0].match(/^\d+.*\d$/) &&
|
|
!_valArray[0].match(_thousandRegEx) &&
|
|
!! _valArray[1].match(/^\d+$/));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* * @deprecated Since in version 1.6.0. It will be deleted in version 2.0
|
|
* `loadCulturesInNode` should be used instead.
|
|
*/
|
|
numbro.loadLanguagesInNode = function() {
|
|
console.warn('`loadLanguagesInNode` is deprecated since version 1.6.0. Use `loadCulturesInNode` instead');
|
|
|
|
numbro.loadCulturesInNode();
|
|
};
|
|
|
|
numbro.loadCulturesInNode = function() {
|
|
// TODO: Rename the folder in 2.0.0
|
|
var cultures = require('./languages');
|
|
|
|
for(var langLocaleCode in cultures) {
|
|
if(langLocaleCode) {
|
|
numbro.culture(langLocaleCode, cultures[langLocaleCode]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/************************************
|
|
Helpers
|
|
************************************/
|
|
|
|
function setCulture(code, values) {
|
|
cultures[code] = values;
|
|
}
|
|
|
|
function chooseCulture(code) {
|
|
currentCulture = code;
|
|
var defaults = cultures[code].defaults;
|
|
if (defaults && defaults.format) {
|
|
numbro.defaultFormat(defaults.format);
|
|
}
|
|
if (defaults && defaults.currencyFormat) {
|
|
numbro.defaultCurrencyFormat(defaults.currencyFormat);
|
|
}
|
|
}
|
|
|
|
function inNodejsRuntime() {
|
|
return (typeof process !== 'undefined') &&
|
|
(process.browser === undefined) &&
|
|
process.title &&
|
|
(
|
|
process.title.indexOf('node') !== -1 ||
|
|
process.title.indexOf('meteor-tool') > 0 ||
|
|
process.title === 'grunt' ||
|
|
process.title === 'gulp'
|
|
) &&
|
|
(typeof require !== 'undefined');
|
|
}
|
|
|
|
/************************************
|
|
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, optInitialValue) {
|
|
|
|
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 = optInitialValue;
|
|
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);
|
|
}
|
|
|
|
/************************************
|
|
Numbro Prototype
|
|
************************************/
|
|
|
|
|
|
numbro.fn = Numbro.prototype = {
|
|
|
|
clone: function() {
|
|
return numbro(this);
|
|
},
|
|
|
|
format: function(inputString, roundingFunction) {
|
|
return formatNumbro(this,
|
|
inputString ? inputString : defaultFormat,
|
|
(roundingFunction !== undefined) ? roundingFunction : Math.round
|
|
);
|
|
},
|
|
|
|
formatCurrency: function(inputString, roundingFunction) {
|
|
return formatCurrency(this,
|
|
cultures[currentCulture].currency.symbol,
|
|
inputString ? inputString : defaultCurrencyFormat,
|
|
(roundingFunction !== undefined) ? roundingFunction : Math.round
|
|
);
|
|
},
|
|
|
|
formatForeignCurrency: function(currencySymbol, inputString, roundingFunction) {
|
|
return formatForeignCurrency(this,
|
|
currencySymbol,
|
|
inputString ? inputString : defaultCurrencyFormat,
|
|
(roundingFunction !== undefined) ? roundingFunction : Math.round
|
|
);
|
|
},
|
|
|
|
unformat: function(inputString) {
|
|
if (typeof inputString === 'number') {
|
|
return inputString;
|
|
} else if (typeof inputString === 'string') {
|
|
var result = unformatNumbro(this, inputString);
|
|
|
|
// Any unparseable string (represented as NaN in the result) is
|
|
// converted into undefined.
|
|
return isNaN(result) ? undefined : result;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
},
|
|
|
|
binaryByteUnits: function() {
|
|
return formatByteUnits(this._value, bytes.binary.suffixes, bytes.binary.scale).suffix;
|
|
},
|
|
|
|
byteUnits: function() {
|
|
return formatByteUnits(this._value, bytes.general.suffixes, bytes.general.scale).suffix;
|
|
},
|
|
|
|
decimalByteUnits: function() {
|
|
return formatByteUnits(this._value, bytes.decimal.suffixes, bytes.decimal.scale).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) {
|
|
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) {
|
|
return accum - corrFactor * curr;
|
|
}
|
|
this._value = [value].reduce(cback, this._value * corrFactor) / corrFactor;
|
|
return this;
|
|
},
|
|
|
|
multiply: function(value) {
|
|
function cback(accum, curr) {
|
|
var corrFactor = correctionFactor(accum, curr),
|
|
result = accum * corrFactor;
|
|
result *= curr * corrFactor;
|
|
result /= corrFactor * corrFactor;
|
|
return result;
|
|
}
|
|
this._value = [this._value, value].reduce(cback, 1);
|
|
return this;
|
|
},
|
|
|
|
divide: function(value) {
|
|
function cback(accum, curr) {
|
|
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(numbro(this._value).subtract(value).value());
|
|
}
|
|
|
|
};
|
|
|
|
/************************************
|
|
Exposing Numbro
|
|
************************************/
|
|
|
|
if (inNodejsRuntime()) {
|
|
//Todo: Rename the folder in 2.0.0
|
|
numbro.loadCulturesInNode();
|
|
}
|
|
|
|
// CommonJS module is defined
|
|
if (hasModule) {
|
|
module.exports = numbro;
|
|
} else {
|
|
/*global ender:false */
|
|
if (typeof ender === 'undefined') {
|
|
// here, `this` means `window` in the browser, or `global` on the server
|
|
// add `numbro` as a global object via a string identifier,
|
|
// for Closure Compiler 'advanced' mode
|
|
this.numbro = numbro;
|
|
}
|
|
|
|
/*global define:false */
|
|
if (typeof define === 'function' && define.amd) {
|
|
define([], function() {
|
|
return numbro;
|
|
});
|
|
}
|
|
}
|
|
|
|
}.call(typeof window === 'undefined' ? this : window));
|