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

/*!
* 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));