UNPKG

21.8 kBJavaScriptView Raw
1/*! @preserve
2 * numeral.js
3 * version : 2.0.0
4 * author : Adam Draper
5 * license : MIT
6 * http://adamwdraper.github.com/Numeral-js/
7 */
8
9(function() {
10 /************************************
11 Variables
12 ************************************/
13
14 var numeral,
15 _,
16 VERSION = '2.0.0',
17 formats = {},
18 locales = {},
19 defaults = {
20 currentLocale: 'en',
21 zeroFormat: null,
22 nullFormat: null,
23 defaultFormat: '0,0'
24 },
25 options = {
26 currentLocale: defaults.currentLocale,
27 zeroFormat: defaults.zeroFormat,
28 nullFormat: defaults.nullFormat,
29 defaultFormat: defaults.defaultFormat
30 };
31
32
33 /************************************
34 Constructors
35 ************************************/
36
37 // Numeral prototype object
38 function Numeral(input, number) {
39 this._input = input;
40
41 this._value = number;
42 }
43
44 numeral = function(input) {
45 var value,
46 kind,
47 unformatFunction,
48 regexp;
49
50 if (numeral.isNumeral(input)) {
51 value = input.value();
52 } else if (input === 0 || typeof input === 'undefined') {
53 value = 0;
54 } else if (input === null || _.isNaN(input)) {
55 value = null;
56 } else if (typeof input === 'string') {
57 if (options.zeroFormat && input === options.zeroFormat) {
58 value = 0;
59 } else if (options.nullFormat && input === options.nullFormat || !input.replace(/[^0-9]+/g, '').length) {
60 value = null;
61 } else {
62 for (kind in formats) {
63 regexp = typeof formats[kind].regexps.unformat === 'function' ? formats[kind].regexps.unformat() : formats[kind].regexps.unformat;
64
65 if (regexp && input.match(regexp)) {
66 unformatFunction = formats[kind].unformat;
67
68 break;
69 }
70 }
71
72 unformatFunction = unformatFunction || numeral._.stringToNumber;
73
74 value = unformatFunction(input);
75 }
76 } else {
77 value = Number(input)|| null;
78 }
79
80 return new Numeral(input, value);
81 };
82
83 // version number
84 numeral.version = VERSION;
85
86 // compare numeral object
87 numeral.isNumeral = function(obj) {
88 return obj instanceof Numeral;
89 };
90
91 // helper functions
92 numeral._ = _ = {
93 // formats numbers separators, decimals places, signs, abbreviations
94 numberToFormat: function(value, format, roundingFunction) {
95 var locale = locales[numeral.options.currentLocale],
96 negP = false,
97 signed = false,
98 optDec = false,
99 abbr = '',
100 trillion = 1000000000000,
101 billion = 1000000000,
102 million = 1000000,
103 thousand = 1000,
104 abbrForce, // force abbreviation
105 abs,
106 min,
107 max,
108 power,
109 int,
110 precision,
111 thousands,
112 decimal = '',
113 neg = false;
114
115 // make sure we never format a null value
116 value = value || 0;
117
118 abs = Math.abs(value);
119
120 // see if we should use parentheses for negative number or if we should prefix with a sign
121 // if both are present we default to parentheses
122 if (numeral._.includes(format, '(')) {
123 negP = true;
124 format = format.slice(1, -1);
125 } else if (numeral._.includes(format, '+')) {
126 signed = true;
127 format = format.replace(/\+/g, '');
128 }
129
130 // see if abbreviation is wanted
131 if (numeral._.includes(format, 'a')) {
132 abbrForce = format.match(/a(k|m|b|t)?/);
133
134 abbrForce = abbrForce ? abbrForce[1] : false;
135
136 // check for space before abbreviation
137 if (numeral._.includes(format, ' a')) {
138 abbr = ' ';
139 }
140
141 format = format.replace(new RegExp(abbr + 'a[kmbt]?'), '');
142
143 if (abs >= trillion && !abbrForce || abbrForce === 't') {
144 // trillion
145 abbr += locale.abbreviations.trillion;
146 value = value / trillion;
147 } else if (abs < trillion && abs >= billion && !abbrForce || abbrForce === 'b') {
148 // billion
149 abbr += locale.abbreviations.billion;
150 value = value / billion;
151 } else if (abs < billion && abs >= million && !abbrForce || abbrForce === 'm') {
152 // million
153 abbr += locale.abbreviations.million;
154 value = value / million;
155 } else if (abs < million && abs >= thousand && !abbrForce || abbrForce === 'k') {
156 // thousand
157 abbr += locale.abbreviations.thousand;
158 value = value / thousand;
159 }
160 }
161
162
163 if (numeral._.includes(format, '[.]')) {
164 optDec = true;
165 format = format.replace('[.]', '.');
166 }
167
168 int = value.toString().split('.')[0];
169 precision = format.split('.')[1];
170 thousands = format.indexOf(',');
171
172 if (precision) {
173 if (numeral._.includes(precision, '[')) {
174 precision = precision.replace(']', '');
175 precision = precision.split('[');
176 decimal = numeral._.toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length);
177 } else {
178 decimal = numeral._.toFixed(value, precision.length, roundingFunction);
179 }
180
181 int = decimal.split('.')[0];
182
183 if (numeral._.includes(decimal, '.')) {
184 decimal = locale.delimiters.decimal + decimal.split('.')[1];
185 } else {
186 decimal = '';
187 }
188
189 if (optDec && Number(decimal.slice(1)) === 0) {
190 decimal = '';
191 }
192 } else {
193 int = numeral._.toFixed(value, null, roundingFunction);
194 }
195
196 // format number
197 if (numeral._.includes(int, '-')) {
198 int = int.slice(1);
199 neg = true;
200 }
201
202 if (thousands > -1) {
203 int = int.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + locale.delimiters.thousands);
204 }
205
206 if (format.indexOf('.') === 0) {
207 int = '';
208 }
209
210 return (negP && neg ? '(' : '') + (!negP && neg ? '-' : '') + (!neg && signed ? '+' : '') + int + decimal + (abbr ? abbr : '') + (negP && neg ? ')' : '');
211 },
212 // unformats numbers separators, decimals places, signs, abbreviations
213 stringToNumber: function(string) {
214 var locale = locales[options.currentLocale],
215 stringOriginal = string,
216 abbreviations = {
217 thousand: 3,
218 million: 6,
219 billion: 9,
220 trillion: 12
221 },
222 abbreviation,
223 value,
224 i,
225 regexp;
226
227 if (options.zeroFormat && string === options.zeroFormat) {
228 value = 0;
229 } else if (options.nullFormat && string === options.nullFormat || !string.replace(/[^0-9]+/g, '').length) {
230 value = null;
231 } else {
232 value = 1;
233
234 if (locale.delimiters.decimal !== '.') {
235 string = string.replace(/\./g, '').replace(locale.delimiters.decimal, '.');
236 }
237
238 for (abbreviation in abbreviations) {
239 regexp = new RegExp('[^a-zA-Z]' + locale.abbreviations[abbreviation] + '(?:\\)|(\\' + locale.currency.symbol + ')?(?:\\))?)?$');
240
241 if (stringOriginal.match(regexp)) {
242 value *= Math.pow(10, abbreviations[abbreviation]);
243 break;
244 }
245 }
246
247 // check for negative number
248 value *= (string.split('-').length + Math.min(string.split('(').length - 1, string.split(')').length - 1)) % 2 ? 1 : -1;
249
250 // remove non numbers
251 string = string.replace(/[^0-9\.]+/g, '');
252
253 value *= Number(string);
254 }
255
256 return value;
257 },
258 isNaN: function(value) {
259 return typeof value === 'number' && isNaN(value);
260 },
261 includes: function(string, search) {
262 return string.indexOf(search) !== -1;
263 },
264 reduce: function(array, callback /*, initialValue*/) {
265 if (this === null) {
266 throw new TypeError('Array.prototype.reduce called on null or undefined');
267 }
268
269 if (typeof callback !== 'function') {
270 throw new TypeError(callback + ' is not a function');
271 }
272
273 var t = Object(array),
274 len = t.length >>> 0,
275 k = 0,
276 value;
277
278 if (arguments.length === 3) {
279 value = arguments[2];
280 } else {
281 while (k < len && !(k in t)) {
282 k++;
283 }
284
285 if (k >= len) {
286 throw new TypeError('Reduce of empty array with no initial value');
287 }
288
289 value = t[k++];
290 }
291 for (; k < len; k++) {
292 if (k in t) {
293 value = callback(value, t[k], k, t);
294 }
295 }
296 return value;
297 },
298 /**
299 * Computes the multiplier necessary to make x >= 1,
300 * effectively eliminating miscalculations caused by
301 * finite precision.
302 */
303 multiplier: function (x) {
304 var parts = x.toString().split('.');
305
306 return parts.length < 2 ? 1 : Math.pow(10, parts[1].length);
307 },
308 /**
309 * Given a variable number of arguments, returns the maximum
310 * multiplier that must be used to normalize an operation involving
311 * all of them.
312 */
313 correctionFactor: function () {
314 var args = Array.prototype.slice.call(arguments);
315
316 return args.reduce(function(accum, next) {
317 var mn = _.multiplier(next);
318 return accum > mn ? accum : mn;
319 }, 1);
320 },
321 /**
322 * Implementation of toFixed() that treats floats more like decimals
323 *
324 * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
325 * problems for accounting- and finance-related software.
326 */
327 toFixed: function(value, maxDecimals, roundingFunction, optionals) {
328 var splitValue = value.toString().split('.'),
329 minDecimals = maxDecimals - (optionals || 0),
330 boundedPrecision,
331 optionalsRegExp,
332 power,
333 output;
334
335 // Use the smallest precision value possible to avoid errors from floating point representation
336 if (splitValue.length === 2) {
337 boundedPrecision = Math.min(Math.max(splitValue[1].length, minDecimals), maxDecimals);
338 } else {
339 boundedPrecision = minDecimals;
340 }
341
342 power = Math.pow(10, boundedPrecision);
343
344 //roundingFunction = (roundingFunction !== undefined ? roundingFunction : Math.round);
345 // Multiply up by precision, round accurately, then divide and use native toFixed():
346 output = (roundingFunction(value * power) / power).toFixed(boundedPrecision);
347
348 if (optionals > maxDecimals - boundedPrecision) {
349 optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$');
350 output = output.replace(optionalsRegExp, '');
351 }
352
353 return output;
354 }
355 };
356
357 // avaliable options
358 numeral.options = options;
359
360 // avaliable formats
361 numeral.formats = formats;
362
363 // avaliable formats
364 numeral.locales = locales;
365
366 // This function sets the current locale. If
367 // no arguments are passed in, it will simply return the current global
368 // locale key.
369 numeral.locale = function(key) {
370 if (!key) {
371 return options.currentLocale;
372 }
373
374 options.currentLocale = key.toLowerCase();
375
376 return numeral;
377 };
378
379 // This function provides access to the loaded locale data. If
380 // no arguments are passed in, it will simply return the current
381 // global locale object.
382 numeral.localeData = function(key) {
383 if (!key) {
384 return locales[options.currentLocale];
385 }
386
387 key = key.toLowerCase();
388
389 if (!locales[key]) {
390 throw new Error('Unknown locale : ' + key);
391 }
392
393 return locales[key];
394 };
395
396 numeral.reset = function() {
397 for (var property in defaults) {
398 options[property] = defaults[property];
399 }
400 };
401
402 numeral.zeroFormat = function(format) {
403 options.zeroFormat = typeof(format) === 'string' ? format : null;
404 };
405
406 numeral.nullFormat = function (format) {
407 options.nullFormat = typeof(format) === 'string' ? format : null;
408 };
409
410 numeral.defaultFormat = function(format) {
411 options.defaultFormat = typeof(format) === 'string' ? format : '0.0';
412 };
413
414 numeral.register = function(type, name, format) {
415 name = name.toLowerCase();
416
417 if (this[type + 's'][name]) {
418 throw new TypeError(name + ' ' + type + ' already registered.');
419 }
420
421 this[type + 's'][name] = format;
422 };
423
424
425 numeral.validate = function(val, culture) {
426 var _decimalSep,
427 _thousandSep,
428 _currSymbol,
429 _valArray,
430 _abbrObj,
431 _thousandRegEx,
432 localeData,
433 temp;
434
435 //coerce val to string
436 if (typeof val !== 'string') {
437 val += '';
438
439 if (console.warn) {
440 console.warn('Numeral.js: Value is not string. It has been co-erced to: ', val);
441 }
442 }
443
444 //trim whitespaces from either sides
445 val = val.trim();
446
447 //if val is just digits return true
448 if (!!val.match(/^\d+$/)) {
449 return true;
450 }
451
452 //if val is empty return false
453 if (val === '') {
454 return false;
455 }
456
457 //get the decimal and thousands separator from numeral.localeData
458 try {
459 //check if the culture is understood by numeral. if not, default it to current locale
460 localeData = numeral.localeData(culture);
461 } catch (e) {
462 localeData = numeral.localeData(numeral.locale());
463 }
464
465 //setup the delimiters and currency symbol based on culture/locale
466 _currSymbol = localeData.currency.symbol;
467 _abbrObj = localeData.abbreviations;
468 _decimalSep = localeData.delimiters.decimal;
469 if (localeData.delimiters.thousands === '.') {
470 _thousandSep = '\\.';
471 } else {
472 _thousandSep = localeData.delimiters.thousands;
473 }
474
475 // validating currency symbol
476 temp = val.match(/^[^\d]+/);
477 if (temp !== null) {
478 val = val.substr(1);
479 if (temp[0] !== _currSymbol) {
480 return false;
481 }
482 }
483
484 //validating abbreviation symbol
485 temp = val.match(/[^\d]+$/);
486 if (temp !== null) {
487 val = val.slice(0, -1);
488 if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million && temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) {
489 return false;
490 }
491 }
492
493 _thousandRegEx = new RegExp(_thousandSep + '{2}');
494
495 if (!val.match(/[^\d.,]/g)) {
496 _valArray = val.split(_decimalSep);
497 if (_valArray.length > 2) {
498 return false;
499 } else {
500 if (_valArray.length < 2) {
501 return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx));
502 } else {
503 if (_valArray[0].length === 1) {
504 return ( !! _valArray[0].match(/^\d+$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/));
505 } else {
506 return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx) && !! _valArray[1].match(/^\d+$/));
507 }
508 }
509 }
510 }
511
512 return false;
513 };
514
515
516 /************************************
517 Numeral Prototype
518 ************************************/
519
520 numeral.fn = Numeral.prototype = {
521 clone: function() {
522 return numeral(this);
523 },
524 format: function(inputString, roundingFunction) {
525 var value = this._value,
526 format = inputString || options.defaultFormat,
527 kind,
528 output,
529 formatFunction;
530
531 // make sure we have a roundingFunction
532 roundingFunction = roundingFunction || Math.round;
533
534 // format based on value
535 if (value === 0 && options.zeroFormat !== null) {
536 output = options.zeroFormat;
537 } else if (value === null && options.nullFormat !== null) {
538 output = options.nullFormat;
539 } else {
540 for (kind in formats) {
541 if (format.match(formats[kind].regexps.format)) {
542 formatFunction = formats[kind].format;
543
544 break;
545 }
546 }
547
548 formatFunction = formatFunction || numeral._.numberToFormat;
549
550 output = formatFunction(value, format, roundingFunction);
551 }
552
553 return output;
554 },
555 value: function() {
556 return this._value;
557 },
558 input: function() {
559 return this._input;
560 },
561 set: function(value) {
562 this._value = Number(value);
563
564 return this;
565 },
566 add: function(value) {
567 var corrFactor = _.correctionFactor.call(null, this._value, value);
568
569 function cback(accum, curr, currI, O) {
570 return accum + Math.round(corrFactor * curr);
571 }
572
573 this._value = _.reduce([this._value, value], cback, 0) / corrFactor;
574
575 return this;
576 },
577 subtract: function(value) {
578 var corrFactor = _.correctionFactor.call(null, this._value, value);
579
580 function cback(accum, curr, currI, O) {
581 return accum - Math.round(corrFactor * curr);
582 }
583
584 this._value = _.reduce([value], cback, Math.round(this._value * corrFactor)) / corrFactor;
585
586 return this;
587 },
588 multiply: function(value) {
589 function cback(accum, curr, currI, O) {
590 var corrFactor = _.correctionFactor(accum, curr);
591 return Math.round(accum * corrFactor) * Math.round(curr * corrFactor) / Math.round(corrFactor * corrFactor);
592 }
593
594 this._value = _.reduce([this._value, value], cback, 1);
595
596 return this;
597 },
598 divide: function(value) {
599 function cback(accum, curr, currI, O) {
600 var corrFactor = _.correctionFactor(accum, curr);
601 return Math.round(accum * corrFactor) / Math.round(curr * corrFactor);
602 }
603
604 this._value = _.reduce([this._value, value], cback);
605
606 return this;
607 },
608 difference: function(value) {
609 return Math.abs(numeral(this._value).subtract(value).value());
610 }
611 };
612
613 /************************************
614 Default Locale && Format
615 ************************************/
616
617 numeral.register('locale', 'en', {
618 delimiters: {
619 thousands: ',',
620 decimal: '.'
621 },
622 abbreviations: {
623 thousand: 'k',
624 million: 'm',
625 billion: 'b',
626 trillion: 't'
627 },
628 ordinal: function(number) {
629 var b = number % 10;
630 return (~~(number % 100 / 10) === 1) ? 'th' :
631 (b === 1) ? 'st' :
632 (b === 2) ? 'nd' :
633 (b === 3) ? 'rd' : 'th';
634 },
635 currency: {
636 symbol: '$'
637 }
638 });
639
640
641 /************************************
642 Exposing Numeral
643 ************************************/
644
645 // CommonJS module is defined
646 if (typeof module !== 'undefined' && module.exports) {
647 module.exports = numeral;
648 }
649
650 /*global ender:false */
651 if (typeof ender === 'undefined') {
652 // here, `this` means `window` in the browser, or `global` on the server
653 // add `numeral` as a global object via a string identifier,
654 // for Closure Compiler 'advanced' mode
655 this['numeral'] = numeral;
656 }
657
658 /*global define:false */
659 if (typeof define === 'function' && define.amd) {
660 define([], function() {
661 return numeral;
662 });
663 }
664}).call(this);