UNPKG

23.9 kBJavaScriptView Raw
1/*!
2 * numeral.js
3 * version : 1.5.3
4 * author : Adam Draper
5 * license : MIT
6 * http://adamwdraper.github.com/Numeral-js/
7 */
8
9(function () {
10
11 /************************************
12 Constants
13 ************************************/
14
15 var numeral,
16 VERSION = '1.5.3',
17 // internal storage for language config files
18 languages = {},
19 currentLanguage = 'en',
20 zeroFormat = null,
21 defaultFormat = '0,0',
22 // check for nodeJS
23 hasModule = (typeof module !== 'undefined' && module.exports);
24
25
26 /************************************
27 Constructors
28 ************************************/
29
30
31 // Numeral prototype object
32 function Numeral (number) {
33 this._value = number;
34 }
35
36 /**
37 * Implementation of toFixed() that treats floats more like decimals
38 *
39 * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
40 * problems for accounting- and finance-related software.
41 */
42 function toFixed (value, precision, roundingFunction, optionals) {
43 var power = Math.pow(10, precision),
44 optionalsRegExp,
45 output;
46
47 //roundingFunction = (roundingFunction !== undefined ? roundingFunction : Math.round);
48 // Multiply up by precision, round accurately, then divide and use native toFixed():
49 output = (roundingFunction(value * power) / power).toFixed(precision);
50
51 if (optionals) {
52 optionalsRegExp = new RegExp('0{1,' + optionals + '}$');
53 output = output.replace(optionalsRegExp, '');
54 }
55
56 return output;
57 }
58
59 /************************************
60 Formatting
61 ************************************/
62
63 // determine what type of formatting we need to do
64 function formatNumeral (n, format, roundingFunction) {
65 var output;
66
67 // figure out what kind of format we are dealing with
68 if (format.indexOf('$') > -1) { // currency!!!!!
69 output = formatCurrency(n, format, roundingFunction);
70 } else if (format.indexOf('%') > -1) { // percentage
71 output = formatPercentage(n, format, roundingFunction);
72 } else if (format.indexOf(':') > -1) { // time
73 output = formatTime(n, format);
74 } else { // plain ol' numbers or bytes
75 output = formatNumber(n._value, format, roundingFunction);
76 }
77
78 // return string
79 return output;
80 }
81
82 // revert to number
83 function unformatNumeral (n, string) {
84 var stringOriginal = string,
85 thousandRegExp,
86 millionRegExp,
87 billionRegExp,
88 trillionRegExp,
89 suffixes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
90 bytesMultiplier = false,
91 power;
92
93 if (string.indexOf(':') > -1) {
94 n._value = unformatTime(string);
95 } else {
96 if (string === zeroFormat) {
97 n._value = 0;
98 } else {
99 if (languages[currentLanguage].delimiters.decimal !== '.') {
100 string = string.replace(/\./g,'').replace(languages[currentLanguage].delimiters.decimal, '.');
101 }
102
103 // see if abbreviations are there so that we can multiply to the correct number
104 thousandRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.thousand + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
105 millionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.million + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
106 billionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.billion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
107 trillionRegExp = new RegExp('[^a-zA-Z]' + languages[currentLanguage].abbreviations.trillion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
108
109 // see if bytes are there so that we can multiply to the correct number
110 for (power = 0; power <= suffixes.length; power++) {
111 bytesMultiplier = (string.indexOf(suffixes[power]) > -1) ? Math.pow(1024, power + 1) : false;
112
113 if (bytesMultiplier) {
114 break;
115 }
116 }
117
118 // do some math to create our number
119 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, ''));
120
121 // round if we are talking about bytes
122 n._value = (bytesMultiplier) ? Math.ceil(n._value) : n._value;
123 }
124 }
125 return n._value;
126 }
127
128 function formatCurrency (n, format, roundingFunction) {
129 var symbolIndex = format.indexOf('$'),
130 openParenIndex = format.indexOf('('),
131 minusSignIndex = format.indexOf('-'),
132 space = '',
133 spliceIndex,
134 output;
135
136 // check for space before or after currency
137 if (format.indexOf(' $') > -1) {
138 space = ' ';
139 format = format.replace(' $', '');
140 } else if (format.indexOf('$ ') > -1) {
141 space = ' ';
142 format = format.replace('$ ', '');
143 } else {
144 format = format.replace('$', '');
145 }
146
147 // format the number
148 output = formatNumber(n._value, format, roundingFunction);
149
150 // position the symbol
151 if (symbolIndex <= 1) {
152 if (output.indexOf('(') > -1 || output.indexOf('-') > -1) {
153 output = output.split('');
154 spliceIndex = 1;
155 if (symbolIndex < openParenIndex || symbolIndex < minusSignIndex){
156 // the symbol appears before the "(" or "-"
157 spliceIndex = 0;
158 }
159 output.splice(spliceIndex, 0, languages[currentLanguage].currency.symbol + space);
160 output = output.join('');
161 } else {
162 output = languages[currentLanguage].currency.symbol + space + output;
163 }
164 } else {
165 if (output.indexOf(')') > -1) {
166 output = output.split('');
167 output.splice(-1, 0, space + languages[currentLanguage].currency.symbol);
168 output = output.join('');
169 } else {
170 output = output + space + languages[currentLanguage].currency.symbol;
171 }
172 }
173
174 return output;
175 }
176
177 function formatPercentage (n, format, roundingFunction) {
178 var space = '',
179 output,
180 value = n._value * 100;
181
182 // check for space before %
183 if (format.indexOf(' %') > -1) {
184 space = ' ';
185 format = format.replace(' %', '');
186 } else {
187 format = format.replace('%', '');
188 }
189
190 output = formatNumber(value, format, roundingFunction);
191
192 if (output.indexOf(')') > -1 ) {
193 output = output.split('');
194 output.splice(-1, 0, space + '%');
195 output = output.join('');
196 } else {
197 output = output + space + '%';
198 }
199
200 return output;
201 }
202
203 function formatTime (n) {
204 var hours = Math.floor(n._value/60/60),
205 minutes = Math.floor((n._value - (hours * 60 * 60))/60),
206 seconds = Math.round(n._value - (hours * 60 * 60) - (minutes * 60));
207 return hours + ':' + ((minutes < 10) ? '0' + minutes : minutes) + ':' + ((seconds < 10) ? '0' + seconds : seconds);
208 }
209
210 function unformatTime (string) {
211 var timeArray = string.split(':'),
212 seconds = 0;
213 // turn hours and minutes into seconds and add them all up
214 if (timeArray.length === 3) {
215 // hours
216 seconds = seconds + (Number(timeArray[0]) * 60 * 60);
217 // minutes
218 seconds = seconds + (Number(timeArray[1]) * 60);
219 // seconds
220 seconds = seconds + Number(timeArray[2]);
221 } else if (timeArray.length === 2) {
222 // minutes
223 seconds = seconds + (Number(timeArray[0]) * 60);
224 // seconds
225 seconds = seconds + Number(timeArray[1]);
226 }
227 return Number(seconds);
228 }
229
230 function formatNumber (value, format, roundingFunction) {
231 var negP = false,
232 signed = false,
233 optDec = false,
234 abbr = '',
235 abbrK = false, // force abbreviation to thousands
236 abbrM = false, // force abbreviation to millions
237 abbrB = false, // force abbreviation to billions
238 abbrT = false, // force abbreviation to trillions
239 abbrForce = false, // force abbreviation
240 bytes = '',
241 ord = '',
242 abs = Math.abs(value),
243 suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
244 min,
245 max,
246 power,
247 w,
248 precision,
249 thousands,
250 d = '',
251 neg = false;
252
253 // check if number is zero and a custom zero format has been set
254 if (value === 0 && zeroFormat !== null) {
255 return zeroFormat;
256 } else {
257 // see if we should use parentheses for negative number or if we should prefix with a sign
258 // if both are present we default to parentheses
259 if (format.indexOf('(') > -1) {
260 negP = true;
261 format = format.slice(1, -1);
262 } else if (format.indexOf('+') > -1) {
263 signed = true;
264 format = format.replace(/\+/g, '');
265 }
266
267 // see if abbreviation is wanted
268 if (format.indexOf('a') > -1) {
269 // check if abbreviation is specified
270 abbrK = format.indexOf('aK') >= 0;
271 abbrM = format.indexOf('aM') >= 0;
272 abbrB = format.indexOf('aB') >= 0;
273 abbrT = format.indexOf('aT') >= 0;
274 abbrForce = abbrK || abbrM || abbrB || abbrT;
275
276 // check for space before abbreviation
277 if (format.indexOf(' a') > -1) {
278 abbr = ' ';
279 format = format.replace(' a', '');
280 } else {
281 format = format.replace('a', '');
282 }
283
284 if (abs >= Math.pow(10, 12) && !abbrForce || abbrT) {
285 // trillion
286 abbr = abbr + languages[currentLanguage].abbreviations.trillion;
287 value = value / Math.pow(10, 12);
288 } else if (abs < Math.pow(10, 12) && abs >= Math.pow(10, 9) && !abbrForce || abbrB) {
289 // billion
290 abbr = abbr + languages[currentLanguage].abbreviations.billion;
291 value = value / Math.pow(10, 9);
292 } else if (abs < Math.pow(10, 9) && abs >= Math.pow(10, 6) && !abbrForce || abbrM) {
293 // million
294 abbr = abbr + languages[currentLanguage].abbreviations.million;
295 value = value / Math.pow(10, 6);
296 } else if (abs < Math.pow(10, 6) && abs >= Math.pow(10, 3) && !abbrForce || abbrK) {
297 // thousand
298 abbr = abbr + languages[currentLanguage].abbreviations.thousand;
299 value = value / Math.pow(10, 3);
300 }
301 }
302
303 // see if we are formatting bytes
304 if (format.indexOf('b') > -1) {
305 // check for space before
306 if (format.indexOf(' b') > -1) {
307 bytes = ' ';
308 format = format.replace(' b', '');
309 } else {
310 format = format.replace('b', '');
311 }
312
313 for (power = 0; power <= suffixes.length; power++) {
314 min = Math.pow(1024, power);
315 max = Math.pow(1024, power+1);
316
317 if (value >= min && value < max) {
318 bytes = bytes + suffixes[power];
319 if (min > 0) {
320 value = value / min;
321 }
322 break;
323 }
324 }
325 }
326
327 // see if ordinal is wanted
328 if (format.indexOf('o') > -1) {
329 // check for space before
330 if (format.indexOf(' o') > -1) {
331 ord = ' ';
332 format = format.replace(' o', '');
333 } else {
334 format = format.replace('o', '');
335 }
336
337 ord = ord + languages[currentLanguage].ordinal(value);
338 }
339
340 if (format.indexOf('[.]') > -1) {
341 optDec = true;
342 format = format.replace('[.]', '.');
343 }
344
345 w = value.toString().split('.')[0];
346 precision = format.split('.')[1];
347 thousands = format.indexOf(',');
348
349 if (precision) {
350 if (precision.indexOf('[') > -1) {
351 precision = precision.replace(']', '');
352 precision = precision.split('[');
353 d = toFixed(value, (precision[0].length + precision[1].length), roundingFunction, precision[1].length);
354 } else {
355 d = toFixed(value, precision.length, roundingFunction);
356 }
357
358 w = d.split('.')[0];
359
360 if (d.split('.')[1].length) {
361 d = languages[currentLanguage].delimiters.decimal + d.split('.')[1];
362 } else {
363 d = '';
364 }
365
366 if (optDec && Number(d.slice(1)) === 0) {
367 d = '';
368 }
369 } else {
370 w = toFixed(value, null, roundingFunction);
371 }
372
373 // format number
374 if (w.indexOf('-') > -1) {
375 w = w.slice(1);
376 neg = true;
377 }
378
379 if (thousands > -1) {
380 w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + languages[currentLanguage].delimiters.thousands);
381 }
382
383 if (format.indexOf('.') === 0) {
384 w = '';
385 }
386
387 return ((negP && neg) ? '(' : '') + ((!negP && neg) ? '-' : '') + ((!neg && signed) ? '+' : '') + w + d + ((ord) ? ord : '') + ((abbr) ? abbr : '') + ((bytes) ? bytes : '') + ((negP && neg) ? ')' : '');
388 }
389 }
390
391 /************************************
392 Top Level Functions
393 ************************************/
394
395 numeral = function (input) {
396 if (numeral.isNumeral(input)) {
397 input = input.value();
398 } else if (input === 0 || typeof input === 'undefined') {
399 input = 0;
400 } else if (!Number(input)) {
401 input = numeral.fn.unformat(input);
402 }
403
404 return new Numeral(Number(input));
405 };
406
407 // version number
408 numeral.version = VERSION;
409
410 // compare numeral object
411 numeral.isNumeral = function (obj) {
412 return obj instanceof Numeral;
413 };
414
415 // This function will load languages and then set the global language. If
416 // no arguments are passed in, it will simply return the current global
417 // language key.
418 numeral.language = function (key, values) {
419 if (!key) {
420 return currentLanguage;
421 }
422
423 if (key && !values) {
424 if(!languages[key]) {
425 throw new Error('Unknown language : ' + key);
426 }
427 currentLanguage = key;
428 }
429
430 if (values || !languages[key]) {
431 loadLanguage(key, values);
432 }
433
434 return numeral;
435 };
436
437 // This function provides access to the loaded language data. If
438 // no arguments are passed in, it will simply return the current
439 // global language object.
440 numeral.languageData = function (key) {
441 if (!key) {
442 return languages[currentLanguage];
443 }
444
445 if (!languages[key]) {
446 throw new Error('Unknown language : ' + key);
447 }
448
449 return languages[key];
450 };
451
452 numeral.language('en', {
453 delimiters: {
454 thousands: ',',
455 decimal: '.'
456 },
457 abbreviations: {
458 thousand: 'k',
459 million: 'm',
460 billion: 'b',
461 trillion: 't'
462 },
463 ordinal: function (number) {
464 var b = number % 10;
465 return (~~ (number % 100 / 10) === 1) ? 'th' :
466 (b === 1) ? 'st' :
467 (b === 2) ? 'nd' :
468 (b === 3) ? 'rd' : 'th';
469 },
470 currency: {
471 symbol: '$'
472 }
473 });
474
475 numeral.zeroFormat = function (format) {
476 zeroFormat = typeof(format) === 'string' ? format : null;
477 };
478
479 numeral.defaultFormat = function (format) {
480 defaultFormat = typeof(format) === 'string' ? format : '0.0';
481 };
482
483 /************************************
484 Helpers
485 ************************************/
486
487 function loadLanguage(key, values) {
488 languages[key] = values;
489 }
490
491 /************************************
492 Floating-point helpers
493 ************************************/
494
495 // The floating-point helper functions and implementation
496 // borrows heavily from sinful.js: http://guipn.github.io/sinful.js/
497
498 /**
499 * Array.prototype.reduce for browsers that don't support it
500 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Compatibility
501 */
502 if ('function' !== typeof Array.prototype.reduce) {
503 Array.prototype.reduce = function (callback, opt_initialValue) {
504 'use strict';
505
506 if (null === this || 'undefined' === typeof this) {
507 // At the moment all modern browsers, that support strict mode, have
508 // native implementation of Array.prototype.reduce. For instance, IE8
509 // does not support strict mode, so this check is actually useless.
510 throw new TypeError('Array.prototype.reduce called on null or undefined');
511 }
512
513 if ('function' !== typeof callback) {
514 throw new TypeError(callback + ' is not a function');
515 }
516
517 var index,
518 value,
519 length = this.length >>> 0,
520 isValueSet = false;
521
522 if (1 < arguments.length) {
523 value = opt_initialValue;
524 isValueSet = true;
525 }
526
527 for (index = 0; length > index; ++index) {
528 if (this.hasOwnProperty(index)) {
529 if (isValueSet) {
530 value = callback(value, this[index], index, this);
531 } else {
532 value = this[index];
533 isValueSet = true;
534 }
535 }
536 }
537
538 if (!isValueSet) {
539 throw new TypeError('Reduce of empty array with no initial value');
540 }
541
542 return value;
543 };
544 }
545
546
547 /**
548 * Computes the multiplier necessary to make x >= 1,
549 * effectively eliminating miscalculations caused by
550 * finite precision.
551 */
552 function multiplier(x) {
553 var parts = x.toString().split('.');
554 if (parts.length < 2) {
555 return 1;
556 }
557 return Math.pow(10, parts[1].length);
558 }
559
560 /**
561 * Given a variable number of arguments, returns the maximum
562 * multiplier that must be used to normalize an operation involving
563 * all of them.
564 */
565 function correctionFactor() {
566 var args = Array.prototype.slice.call(arguments);
567 return args.reduce(function (prev, next) {
568 var mp = multiplier(prev),
569 mn = multiplier(next);
570 return mp > mn ? mp : mn;
571 }, -Infinity);
572 }
573
574
575 /************************************
576 Numeral Prototype
577 ************************************/
578
579
580 numeral.fn = Numeral.prototype = {
581
582 clone : function () {
583 return numeral(this);
584 },
585
586 format : function (inputString, roundingFunction) {
587 return formatNumeral(this,
588 inputString ? inputString : defaultFormat,
589 (roundingFunction !== undefined) ? roundingFunction : Math.round
590 );
591 },
592
593 unformat : function (inputString) {
594 if (Object.prototype.toString.call(inputString) === '[object Number]') {
595 return inputString;
596 }
597 return unformatNumeral(this, inputString ? inputString : defaultFormat);
598 },
599
600 value : function () {
601 return this._value;
602 },
603
604 valueOf : function () {
605 return this._value;
606 },
607
608 set : function (value) {
609 this._value = Number(value);
610 return this;
611 },
612
613 add : function (value) {
614 var corrFactor = correctionFactor.call(null, this._value, value);
615 function cback(accum, curr, currI, O) {
616 return accum + corrFactor * curr;
617 }
618 this._value = [this._value, value].reduce(cback, 0) / corrFactor;
619 return this;
620 },
621
622 subtract : function (value) {
623 var corrFactor = correctionFactor.call(null, this._value, value);
624 function cback(accum, curr, currI, O) {
625 return accum - corrFactor * curr;
626 }
627 this._value = [value].reduce(cback, this._value * corrFactor) / corrFactor;
628 return this;
629 },
630
631 multiply : function (value) {
632 function cback(accum, curr, currI, O) {
633 var corrFactor = correctionFactor(accum, curr);
634 return (accum * corrFactor) * (curr * corrFactor) /
635 (corrFactor * corrFactor);
636 }
637 this._value = [this._value, value].reduce(cback, 1);
638 return this;
639 },
640
641 divide : function (value) {
642 function cback(accum, curr, currI, O) {
643 var corrFactor = correctionFactor(accum, curr);
644 return (accum * corrFactor) / (curr * corrFactor);
645 }
646 this._value = [this._value, value].reduce(cback);
647 return this;
648 },
649
650 difference : function (value) {
651 return Math.abs(numeral(this._value).subtract(value).value());
652 }
653
654 };
655
656 /************************************
657 Exposing Numeral
658 ************************************/
659
660 // CommonJS module is defined
661 if (hasModule) {
662 module.exports = numeral;
663 }
664
665 /*global ender:false */
666 if (typeof ender === 'undefined') {
667 // here, `this` means `window` in the browser, or `global` on the server
668 // add `numeral` as a global object via a string identifier,
669 // for Closure Compiler 'advanced' mode
670 this['numeral'] = numeral;
671 }
672
673 /*global define:false */
674 if (typeof define === 'function' && define.amd) {
675 define([], function () {
676 return numeral;
677 });
678 }
679}).call(this);