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

  1. /*!
  2. * numbro.js
  3. * version : 1.11.0
  4. * author : Företagsplatsen AB
  5. * license : MIT
  6. * http://www.foretagsplatsen.se
  7. */
  8. (function () {
  9. 'use strict';
  10. /************************************
  11. Constants
  12. ************************************/
  13. var numbro,
  14. VERSION = '1.11.0',
  15. binarySuffixes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'],
  16. decimalSuffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  17. bytes = {
  18. general: { scale: 1024, suffixes: decimalSuffixes, marker: 'bd' },
  19. binary: { scale: 1024, suffixes: binarySuffixes, marker: 'b' },
  20. decimal: { scale: 1000, suffixes: decimalSuffixes, marker: 'd' }
  21. },
  22. // general must be before the others because it reuses their characters!
  23. byteFormatOrder = [ bytes.general, bytes.binary, bytes.decimal ],
  24. // internal storage for culture config files
  25. cultures = {},
  26. // Todo: Remove in 2.0.0
  27. languages = cultures,
  28. currentCulture = 'en-US',
  29. zeroFormat = null,
  30. defaultFormat = '0,0',
  31. defaultCurrencyFormat = '0$',
  32. // check for nodeJS
  33. hasModule = (typeof module !== 'undefined' && module.exports),
  34. // default culture
  35. enUS = {
  36. delimiters: {
  37. thousands: ',',
  38. decimal: '.'
  39. },
  40. abbreviations: {
  41. thousand: 'k',
  42. million: 'm',
  43. billion: 'b',
  44. trillion: 't'
  45. },
  46. ordinal: function(number) {
  47. var b = number % 10;
  48. return (~~(number % 100 / 10) === 1) ? 'th' :
  49. (b === 1) ? 'st' :
  50. (b === 2) ? 'nd' :
  51. (b === 3) ? 'rd' : 'th';
  52. },
  53. currency: {
  54. symbol: '$',
  55. position: 'prefix'
  56. },
  57. defaults: {
  58. currencyFormat: ',0000 a'
  59. },
  60. formats: {
  61. fourDigits: '0000 a',
  62. fullWithTwoDecimals: '$ ,0.00',
  63. fullWithTwoDecimalsNoCurrency: ',0.00'
  64. }
  65. };
  66. /************************************
  67. Constructors
  68. ************************************/
  69. // Numbro prototype object
  70. function Numbro(number) {
  71. this._value = number;
  72. }
  73. function numberLength(number) {
  74. if (number === 0) { return 1; }
  75. return Math.floor(Math.log(Math.abs(number)) / Math.LN10) + 1;
  76. }
  77. function zeroes(count) {
  78. var i, ret = '';
  79. for (i = 0; i < count; i++) {
  80. ret += '0';
  81. }
  82. return ret;
  83. }
  84. /**
  85. * Implementation of toFixed() for numbers with exponents
  86. * This function may return negative representations for zero values e.g. "-0.0"
  87. */
  88. function toFixedLargeSmall(value, precision) {
  89. var mantissa,
  90. beforeDec,
  91. afterDec,
  92. exponent,
  93. prefix,
  94. endStr,
  95. zerosStr,
  96. str;
  97. str = value.toString();
  98. mantissa = str.split('e')[0];
  99. exponent = str.split('e')[1];
  100. beforeDec = mantissa.split('.')[0];
  101. afterDec = mantissa.split('.')[1] || '';
  102. if (+exponent > 0) {
  103. // exponent is positive - add zeros after the numbers
  104. str = beforeDec + afterDec + zeroes(exponent - afterDec.length);
  105. } else {
  106. // exponent is negative
  107. if (+beforeDec < 0) {
  108. prefix = '-0';
  109. } else {
  110. prefix = '0';
  111. }
  112. // tack on the decimal point if needed
  113. if (precision > 0) {
  114. prefix += '.';
  115. }
  116. zerosStr = zeroes((-1 * exponent) - 1);
  117. // substring off the end to satisfy the precision
  118. endStr = (zerosStr + Math.abs(beforeDec) + afterDec).substr(0, precision);
  119. str = prefix + endStr;
  120. }
  121. // only add percision 0's if the exponent is positive
  122. if (+exponent > 0 && precision > 0) {
  123. str += '.' + zeroes(precision);
  124. }
  125. return str;
  126. }
  127. /**
  128. * Implementation of toFixed() that treats floats more like decimals
  129. *
  130. * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
  131. * problems for accounting- and finance-related software.
  132. *
  133. * Also removes negative signs for zero-formatted numbers. e.g. -0.01 w/ precision 1 -> 0.0
  134. */
  135. function toFixed(value, precision, roundingFunction, optionals) {
  136. var power = Math.pow(10, precision),
  137. optionalsRegExp,
  138. output;
  139. if (value.toString().indexOf('e') > -1) {
  140. // toFixed returns scientific notation for numbers above 1e21 and below 1e-7
  141. output = toFixedLargeSmall(value, precision);
  142. // remove the leading negative sign if it exists and should not be present (e.g. -0.00)
  143. if (output.charAt(0) === '-' && +output >= 0) {
  144. output = output.substr(1); // chop off the '-'
  145. }
  146. }
  147. else {
  148. // Multiply up by precision, round accurately, then divide and use native toFixed():
  149. output = (roundingFunction(value + 'e+' + precision) / power).toFixed(precision);
  150. }
  151. if (optionals) {
  152. optionalsRegExp = new RegExp('0{1,' + optionals + '}$');
  153. output = output.replace(optionalsRegExp, '');
  154. }
  155. return output;
  156. }
  157. /************************************
  158. Formatting
  159. ************************************/
  160. // determine what type of formatting we need to do
  161. function formatNumbro(n, format, roundingFunction) {
  162. var output,
  163. escapedFormat = format.replace(/\{[^\{\}]*\}/g, '');
  164. // figure out what kind of format we are dealing with
  165. if (escapedFormat.indexOf('$') > -1) { // currency!!!!!
  166. output = formatCurrency(n, cultures[currentCulture].currency.symbol, format, roundingFunction);
  167. } else if (escapedFormat.indexOf('%') > -1) { // percentage
  168. output = formatPercentage(n, format, roundingFunction);
  169. } else if (escapedFormat.indexOf(':') > -1) { // time
  170. output = formatTime(n, format);
  171. } else { // plain ol' numbers or bytes
  172. output = formatNumber(n._value, format, roundingFunction);
  173. }
  174. // return string
  175. return output;
  176. }
  177. // revert to number
  178. function unformatNumbro(n, string) {
  179. var stringOriginal = string,
  180. thousandRegExp,
  181. millionRegExp,
  182. billionRegExp,
  183. trillionRegExp,
  184. bytesMultiplier = false,
  185. power;
  186. if (string.indexOf(':') > -1) {
  187. n._value = unformatTime(string);
  188. } else {
  189. if (string === zeroFormat) {
  190. n._value = 0;
  191. } else {
  192. if (cultures[currentCulture].delimiters.decimal !== '.') {
  193. string = string.replace(/\./g, '').replace(cultures[currentCulture].delimiters.decimal, '.');
  194. }
  195. // see if abbreviations are there so that we can multiply to the correct number
  196. thousandRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.thousand +
  197. '(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
  198. millionRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.million +
  199. '(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
  200. billionRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.billion +
  201. '(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
  202. trillionRegExp = new RegExp('[^a-zA-Z]' + cultures[currentCulture].abbreviations.trillion +
  203. '(?:\\)|(\\' + cultures[currentCulture].currency.symbol + ')?(?:\\))?)?$');
  204. // see if bytes are there so that we can multiply to the correct number
  205. for (power = 1; power < binarySuffixes.length && !bytesMultiplier; ++power) {
  206. if (string.indexOf(binarySuffixes[power]) > -1) {
  207. bytesMultiplier = Math.pow(1024, power);
  208. } else if (string.indexOf(decimalSuffixes[power]) > -1) {
  209. bytesMultiplier = Math.pow(1000, power);
  210. }
  211. }
  212. var str = string.replace(/[^0-9\.]+/g, '');
  213. if (str === '') {
  214. // An empty string is not a number.
  215. n._value = NaN;
  216. } else {
  217. // do some math to create our number
  218. n._value = ((bytesMultiplier) ? bytesMultiplier : 1) *
  219. ((stringOriginal.match(thousandRegExp)) ? Math.pow(10, 3) : 1) *
  220. ((stringOriginal.match(millionRegExp)) ? Math.pow(10, 6) : 1) *
  221. ((stringOriginal.match(billionRegExp)) ? Math.pow(10, 9) : 1) *
  222. ((stringOriginal.match(trillionRegExp)) ? Math.pow(10, 12) : 1) *
  223. ((string.indexOf('%') > -1) ? 0.01 : 1) *
  224. (((string.split('-').length +
  225. Math.min(string.split('(').length - 1, string.split(')').length - 1)) % 2) ? 1 : -1) *
  226. Number(str);
  227. // round if we are talking about bytes
  228. n._value = (bytesMultiplier) ? Math.ceil(n._value) : n._value;
  229. }
  230. }
  231. }
  232. return n._value;
  233. }
  234. function formatCurrency(n, currencySymbol, originalFormat, roundingFunction) {
  235. var format = originalFormat,
  236. symbolIndex = format.indexOf('$'),
  237. openParenIndex = format.indexOf('('),
  238. plusSignIndex = format.indexOf('+'),
  239. minusSignIndex = format.indexOf('-'),
  240. space = '',
  241. decimalSeparator = '',
  242. spliceIndex,
  243. output;
  244. if(format.indexOf('$') === -1){
  245. // Use defaults instead of the format provided
  246. if (cultures[currentCulture].currency.position === 'infix') {
  247. decimalSeparator = currencySymbol;
  248. if (cultures[currentCulture].currency.spaceSeparated) {
  249. decimalSeparator = ' ' + decimalSeparator + ' ';
  250. }
  251. } else if (cultures[currentCulture].currency.spaceSeparated) {
  252. space = ' ';
  253. }
  254. } else {
  255. // check for space before or after currency
  256. if (format.indexOf(' $') > -1) {
  257. space = ' ';
  258. format = format.replace(' $', '');
  259. } else if (format.indexOf('$ ') > -1) {
  260. space = ' ';
  261. format = format.replace('$ ', '');
  262. } else {
  263. format = format.replace('$', '');
  264. }
  265. }
  266. // Format The Number
  267. output = formatNumber(n._value, format, roundingFunction, decimalSeparator);
  268. if (originalFormat.indexOf('$') === -1) {
  269. // Use defaults instead of the format provided
  270. switch (cultures[currentCulture].currency.position) {
  271. case 'postfix':
  272. if (output.indexOf(')') > -1) {
  273. output = output.split('');
  274. output.splice(-1, 0, space + currencySymbol);
  275. output = output.join('');
  276. } else {
  277. output = output + space + currencySymbol;
  278. }
  279. break;
  280. case 'infix':
  281. break;
  282. case 'prefix':
  283. if (output.indexOf('(') > -1 || output.indexOf('-') > -1) {
  284. output = output.split('');
  285. spliceIndex = Math.max(openParenIndex, minusSignIndex) + 1;
  286. output.splice(spliceIndex, 0, currencySymbol + space);
  287. output = output.join('');
  288. } else {
  289. output = currencySymbol + space + output;
  290. }
  291. break;
  292. default:
  293. throw Error('Currency position should be among ["prefix", "infix", "postfix"]');
  294. }
  295. } else {
  296. // position the symbol
  297. if (symbolIndex <= 1) {
  298. if (output.indexOf('(') > -1 || output.indexOf('+') > -1 || output.indexOf('-') > -1) {
  299. output = output.split('');
  300. spliceIndex = 1;
  301. if (symbolIndex < openParenIndex || symbolIndex < plusSignIndex || symbolIndex < minusSignIndex) {
  302. // the symbol appears before the "(", "+" or "-"
  303. spliceIndex = 0;
  304. }
  305. output.splice(spliceIndex, 0, currencySymbol + space);
  306. output = output.join('');
  307. } else {
  308. output = currencySymbol + space + output;
  309. }
  310. } else {
  311. if (output.indexOf(')') > -1) {
  312. output = output.split('');
  313. output.splice(-1, 0, space + currencySymbol);
  314. output = output.join('');
  315. } else {
  316. output = output + space + currencySymbol;
  317. }
  318. }
  319. }
  320. return output;
  321. }
  322. function formatForeignCurrency(n, foreignCurrencySymbol, originalFormat, roundingFunction) {
  323. return formatCurrency(n, foreignCurrencySymbol, originalFormat, roundingFunction);
  324. }
  325. function formatPercentage(n, format, roundingFunction) {
  326. var space = '',
  327. output,
  328. value = n._value * 100;
  329. // check for space before %
  330. if (format.indexOf(' %') > -1) {
  331. space = ' ';
  332. format = format.replace(' %', '');
  333. } else {
  334. format = format.replace('%', '');
  335. }
  336. output = formatNumber(value, format, roundingFunction);
  337. if (output.indexOf(')') > -1) {
  338. output = output.split('');
  339. output.splice(-1, 0, space + '%');
  340. output = output.join('');
  341. } else {
  342. output = output + space + '%';
  343. }
  344. return output;
  345. }
  346. function formatTime(n) {
  347. var hours = Math.floor(n._value / 60 / 60),
  348. minutes = Math.floor((n._value - (hours * 60 * 60)) / 60),
  349. seconds = Math.round(n._value - (hours * 60 * 60) - (minutes * 60));
  350. return hours + ':' +
  351. ((minutes < 10) ? '0' + minutes : minutes) + ':' +
  352. ((seconds < 10) ? '0' + seconds : seconds);
  353. }
  354. function unformatTime(string) {
  355. var timeArray = string.split(':'),
  356. seconds = 0;
  357. // turn hours and minutes into seconds and add them all up
  358. if (timeArray.length === 3) {
  359. // hours
  360. seconds = seconds + (Number(timeArray[0]) * 60 * 60);
  361. // minutes
  362. seconds = seconds + (Number(timeArray[1]) * 60);
  363. // seconds
  364. seconds = seconds + Number(timeArray[2]);
  365. } else if (timeArray.length === 2) {
  366. // minutes
  367. seconds = seconds + (Number(timeArray[0]) * 60);
  368. // seconds
  369. seconds = seconds + Number(timeArray[1]);
  370. }
  371. return Number(seconds);
  372. }
  373. function formatByteUnits (value, suffixes, scale) {
  374. var suffix = suffixes[0],
  375. power,
  376. min,
  377. max,
  378. abs = Math.abs(value);
  379. if (abs >= scale) {
  380. for (power = 1; power < suffixes.length; ++power) {
  381. min = Math.pow(scale, power);
  382. max = Math.pow(scale, power + 1);
  383. if (abs >= min && abs < max) {
  384. suffix = suffixes[power];
  385. value = value / min;
  386. break;
  387. }
  388. }
  389. // values greater than or equal to [scale] YB never set the suffix
  390. if (suffix === suffixes[0]) {
  391. value = value / Math.pow(scale, suffixes.length - 1);
  392. suffix = suffixes[suffixes.length - 1];
  393. }
  394. }
  395. return { value: value, suffix: suffix };
  396. }
  397. function formatNumber (value, format, roundingFunction, sep) {
  398. var negP = false,
  399. signed = false,
  400. optDec = false,
  401. abbr = '',
  402. abbrK = false, // force abbreviation to thousands
  403. abbrM = false, // force abbreviation to millions
  404. abbrB = false, // force abbreviation to billions
  405. abbrT = false, // force abbreviation to trillions
  406. abbrForce = false, // force abbreviation
  407. bytes = '',
  408. byteFormat,
  409. units,
  410. ord = '',
  411. abs = Math.abs(value),
  412. totalLength,
  413. length,
  414. minimumPrecision,
  415. pow,
  416. w,
  417. intPrecision,
  418. precision,
  419. prefix,
  420. postfix,
  421. thousands,
  422. d = '',
  423. forcedNeg = false,
  424. neg = false,
  425. indexOpenP,
  426. indexMinus,
  427. paren = '',
  428. minlen,
  429. i;
  430. // check if number is zero and a custom zero format has been set
  431. if (value === 0 && zeroFormat !== null) {
  432. return zeroFormat;
  433. }
  434. if (!isFinite(value)) {
  435. return '' + value;
  436. }
  437. if (format.indexOf('{') === 0) {
  438. var end = format.indexOf('}');
  439. if (end === -1) {
  440. throw Error('Format should also contain a "}"');
  441. }
  442. prefix = format.slice(1, end);
  443. format = format.slice(end + 1);
  444. } else {
  445. prefix = '';
  446. }
  447. if (format.indexOf('}') === format.length - 1 && format.length) {
  448. var start = format.indexOf('{');
  449. if (start === -1) {
  450. throw Error('Format should also contain a "{"');
  451. }
  452. postfix = format.slice(start + 1, -1);
  453. format = format.slice(0, start + 1);
  454. } else {
  455. postfix = '';
  456. }
  457. // check for min length
  458. var info;
  459. if (format.indexOf('.') === -1) {
  460. info = format.match(/([0-9]+).*/);
  461. } else {
  462. info = format.match(/([0-9]+)\..*/);
  463. }
  464. minlen = info === null ? -1 : info[1].length;
  465. // see if we should use parentheses for negative number or if we should prefix with a sign
  466. // if both are present we default to parentheses
  467. if (format.indexOf('-') !== -1) {
  468. forcedNeg = true;
  469. }
  470. if (format.indexOf('(') > -1) {
  471. negP = true;
  472. format = format.slice(1, -1);
  473. } else if (format.indexOf('+') > -1) {
  474. signed = true;
  475. format = format.replace(/\+/g, '');
  476. }
  477. // see if abbreviation is wanted
  478. if (format.indexOf('a') > -1) {
  479. intPrecision = format.split('.')[0].match(/[0-9]+/g) || ['0'];
  480. intPrecision = parseInt(intPrecision[0], 10);
  481. // check if abbreviation is specified
  482. abbrK = format.indexOf('aK') >= 0;
  483. abbrM = format.indexOf('aM') >= 0;
  484. abbrB = format.indexOf('aB') >= 0;
  485. abbrT = format.indexOf('aT') >= 0;
  486. abbrForce = abbrK || abbrM || abbrB || abbrT;
  487. // check for space before abbreviation
  488. if (format.indexOf(' a') > -1) {
  489. abbr = ' ';
  490. format = format.replace(' a', '');
  491. } else {
  492. format = format.replace('a', '');
  493. }
  494. totalLength = numberLength(value);
  495. minimumPrecision = totalLength % 3;
  496. minimumPrecision = minimumPrecision === 0 ? 3 : minimumPrecision;
  497. if (intPrecision && abs !== 0) {
  498. pow = 3 * ~~((Math.min(intPrecision, totalLength) - minimumPrecision) / 3);
  499. abs = abs / Math.pow(10, pow);
  500. }
  501. if (totalLength !== intPrecision) {
  502. if (abs >= Math.pow(10, 12) && !abbrForce || abbrT) {
  503. // trillion
  504. abbr = abbr + cultures[currentCulture].abbreviations.trillion;
  505. value = value / Math.pow(10, 12);
  506. } else if (abs < Math.pow(10, 12) && abs >= Math.pow(10, 9) && !abbrForce || abbrB) {
  507. // billion
  508. abbr = abbr + cultures[currentCulture].abbreviations.billion;
  509. value = value / Math.pow(10, 9);
  510. } else if (abs < Math.pow(10, 9) && abs >= Math.pow(10, 6) && !abbrForce || abbrM) {
  511. // million
  512. abbr = abbr + cultures[currentCulture].abbreviations.million;
  513. value = value / Math.pow(10, 6);
  514. } else if (abs < Math.pow(10, 6) && abs >= Math.pow(10, 3) && !abbrForce || abbrK) {
  515. // thousand
  516. abbr = abbr + cultures[currentCulture].abbreviations.thousand;
  517. value = value / Math.pow(10, 3);
  518. }
  519. }
  520. length = numberLength(value);
  521. if (intPrecision && length < intPrecision && format.indexOf('.') === -1) {
  522. format += '[.]';
  523. format += zeroes(intPrecision - length);
  524. }
  525. }
  526. // see if we are formatting
  527. // binary-decimal bytes (1024 MB), binary bytes (1024 MiB), or decimal bytes (1000 MB)
  528. for (i = 0; i < byteFormatOrder.length; ++i) {
  529. byteFormat = byteFormatOrder[i];
  530. if (format.indexOf(byteFormat.marker) > -1) {
  531. // check for space before
  532. if (format.indexOf(' ' + byteFormat.marker) >-1) {
  533. bytes = ' ';
  534. }
  535. // remove the marker (with the space if it had one)
  536. format = format.replace(bytes + byteFormat.marker, '');
  537. units = formatByteUnits(value, byteFormat.suffixes, byteFormat.scale);
  538. value = units.value;
  539. bytes = bytes + units.suffix;
  540. break;
  541. }
  542. }
  543. // see if ordinal is wanted
  544. if (format.indexOf('o') > -1) {
  545. // check for space before
  546. if (format.indexOf(' o') > -1) {
  547. ord = ' ';
  548. format = format.replace(' o', '');
  549. } else {
  550. format = format.replace('o', '');
  551. }
  552. if (cultures[currentCulture].ordinal) {
  553. ord = ord + cultures[currentCulture].ordinal(value);
  554. }
  555. }
  556. if (format.indexOf('[.]') > -1) {
  557. optDec = true;
  558. format = format.replace('[.]', '.');
  559. }
  560. precision = format.split('.')[1];
  561. thousands = format.indexOf(',');
  562. if (precision) {
  563. var dSplit = [];
  564. if (precision.indexOf('*') !== -1) {
  565. d = value.toString();
  566. dSplit = d.split('.');
  567. if (dSplit.length > 1) {
  568. d = toFixed(value, dSplit[1].length, roundingFunction);
  569. }
  570. } else {
  571. if (precision.indexOf('[') > -1) {
  572. precision = precision.replace(']', '');
  573. precision = precision.split('[');
  574. d = toFixed(value, (precision[0].length + precision[1].length), roundingFunction,
  575. precision[1].length);
  576. } else {
  577. d = toFixed(value, precision.length, roundingFunction);
  578. }
  579. }
  580. dSplit = d.split('.');
  581. w = dSplit[0];
  582. if (dSplit.length > 1 && dSplit[1].length) {
  583. var p = sep ? abbr + sep : cultures[currentCulture].delimiters.decimal;
  584. d = p + dSplit[1];
  585. } else {
  586. d = '';
  587. }
  588. if (optDec && Number(d.slice(1)) === 0) {
  589. d = '';
  590. }
  591. } else {
  592. w = toFixed(value, 0, roundingFunction);
  593. }
  594. // format number
  595. if (w.indexOf('-') > -1) {
  596. w = w.slice(1);
  597. neg = true;
  598. }
  599. if (w.length < minlen) {
  600. w = zeroes(minlen - w.length) + w;
  601. }
  602. if (thousands > -1) {
  603. w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' +
  604. cultures[currentCulture].delimiters.thousands);
  605. }
  606. if (format.indexOf('.') === 0) {
  607. w = '';
  608. }
  609. indexOpenP = format.indexOf('(');
  610. indexMinus = format.indexOf('-');
  611. if (indexOpenP < indexMinus) {
  612. paren = ((negP && neg) ? '(' : '') + (((forcedNeg && neg) || (!negP && neg)) ? '-' : '');
  613. } else {
  614. paren = (((forcedNeg && neg) || (!negP && neg)) ? '-' : '') + ((negP && neg) ? '(' : '');
  615. }
  616. return prefix +
  617. paren + ((!neg && signed && value !== 0) ? '+' : '') +
  618. w + d +
  619. ((ord) ? ord : '') +
  620. ((abbr && !sep) ? abbr : '') +
  621. ((bytes) ? bytes : '') +
  622. ((negP && neg) ? ')' : '') +
  623. postfix;
  624. }
  625. /************************************
  626. Top Level Functions
  627. ************************************/
  628. numbro = function(input) {
  629. if (numbro.isNumbro(input)) {
  630. input = input.value();
  631. } else if (typeof input === 'string' || typeof input === 'number') {
  632. input = numbro.fn.unformat(input);
  633. } else {
  634. input = NaN;
  635. }
  636. return new Numbro(Number(input));
  637. };
  638. // version number
  639. numbro.version = VERSION;
  640. // compare numbro object
  641. numbro.isNumbro = function(obj) {
  642. return obj instanceof Numbro;
  643. };
  644. /**
  645. * This function allow the user to set a new language with a fallback if
  646. * the language does not exist. If no fallback language is provided,
  647. * it fallbacks to english.
  648. *
  649. * @deprecated Since in version 1.6.0. It will be deleted in version 2.0
  650. * `setCulture` should be used instead.
  651. */
  652. numbro.setLanguage = function(newLanguage, fallbackLanguage) {
  653. console.warn('`setLanguage` is deprecated since version 1.6.0. Use `setCulture` instead');
  654. var key = newLanguage,
  655. prefix = newLanguage.split('-')[0],
  656. matchingLanguage = null;
  657. if (!languages[key]) {
  658. Object.keys(languages).forEach(function(language) {
  659. if (!matchingLanguage && language.split('-')[0] === prefix) {
  660. matchingLanguage = language;
  661. }
  662. });
  663. key = matchingLanguage || fallbackLanguage || 'en-US';
  664. }
  665. chooseCulture(key);
  666. };
  667. /**
  668. * This function allow the user to set a new culture with a fallback if
  669. * the culture does not exist. If no fallback culture is provided,
  670. * it falls back to "en-US".
  671. */
  672. numbro.setCulture = function(newCulture, fallbackCulture) {
  673. var key = newCulture,
  674. suffix = newCulture.split('-')[1],
  675. matchingCulture = null;
  676. if (!cultures[key]) {
  677. if (suffix) {
  678. Object.keys(cultures).forEach(function(language) {
  679. if (!matchingCulture && language.split('-')[1] === suffix) {
  680. matchingCulture = language;
  681. }
  682. });
  683. }
  684. key = matchingCulture || fallbackCulture || 'en-US';
  685. }
  686. chooseCulture(key);
  687. };
  688. /**
  689. * This function will load languages and then set the global language. If
  690. * no arguments are passed in, it will simply return the current global
  691. * language key.
  692. *
  693. * @deprecated Since in version 1.6.0. It will be deleted in version 2.0
  694. * `culture` should be used instead.
  695. */
  696. numbro.language = function(key, values) {
  697. console.warn('`language` is deprecated since version 1.6.0. Use `culture` instead');
  698. if (!key) {
  699. return currentCulture;
  700. }
  701. if (key && !values) {
  702. if (!languages[key]) {
  703. throw new Error('Unknown language : ' + key);
  704. }
  705. chooseCulture(key);
  706. }
  707. if (values || !languages[key]) {
  708. setCulture(key, values);
  709. }
  710. return numbro;
  711. };
  712. /**
  713. * This function will load cultures and then set the global culture. If
  714. * no arguments are passed in, it will simply return the current global
  715. * culture code.
  716. */
  717. numbro.culture = function(code, values) {
  718. if (!code) {
  719. return currentCulture;
  720. }
  721. if (code && !values) {
  722. if (!cultures[code]) {
  723. throw new Error('Unknown culture : ' + code);
  724. }
  725. chooseCulture(code);
  726. }
  727. if (values || !cultures[code]) {
  728. setCulture(code, values);
  729. }
  730. return numbro;
  731. };
  732. /**
  733. * This function provides access to the loaded language data. If
  734. * no arguments are passed in, it will simply return the current
  735. * global language object.
  736. *
  737. * @deprecated Since in version 1.6.0. It will be deleted in version 2.0
  738. * `culture` should be used instead.
  739. */
  740. numbro.languageData = function(key) {
  741. console.warn('`languageData` is deprecated since version 1.6.0. Use `cultureData` instead');
  742. if (!key) {
  743. return languages[currentCulture];
  744. }
  745. if (!languages[key]) {
  746. throw new Error('Unknown language : ' + key);
  747. }
  748. return languages[key];
  749. };
  750. /**
  751. * This function provides access to the loaded culture data. If
  752. * no arguments are passed in, it will simply return the current
  753. * global culture object.
  754. */
  755. numbro.cultureData = function(code) {
  756. if (!code) {
  757. return cultures[currentCulture];
  758. }
  759. if (!cultures[code]) {
  760. throw new Error('Unknown culture : ' + code);
  761. }
  762. return cultures[code];
  763. };
  764. numbro.culture('en-US', enUS);
  765. /**
  766. * @deprecated Since in version 1.6.0. It will be deleted in version 2.0
  767. * `cultures` should be used instead.
  768. */
  769. numbro.languages = function() {
  770. console.warn('`languages` is deprecated since version 1.6.0. Use `cultures` instead');
  771. return languages;
  772. };
  773. numbro.cultures = function() {
  774. return cultures;
  775. };
  776. numbro.zeroFormat = function(format) {
  777. zeroFormat = typeof(format) === 'string' ? format : null;
  778. };
  779. numbro.defaultFormat = function(format) {
  780. defaultFormat = typeof(format) === 'string' ? format : '0.0';
  781. };
  782. numbro.defaultCurrencyFormat = function (format) {
  783. defaultCurrencyFormat = typeof(format) === 'string' ? format : '0$';
  784. };
  785. numbro.validate = function(val, culture) {
  786. var _decimalSep,
  787. _thousandSep,
  788. _currSymbol,
  789. _valArray,
  790. _abbrObj,
  791. _thousandRegEx,
  792. cultureData,
  793. temp;
  794. //coerce val to string
  795. if (typeof val !== 'string') {
  796. val += '';
  797. if (console.warn) {
  798. console.warn('Numbro.js: Value is not string. It has been co-erced to: ', val);
  799. }
  800. }
  801. //trim whitespaces from either sides
  802. val = val.trim();
  803. //replace the initial '+' or '-' sign if present
  804. val = val.replace(/^[+-]?/, '');
  805. //if val is just digits return true
  806. if ( !! val.match(/^\d+$/)) {
  807. return true;
  808. }
  809. //if val is empty return false
  810. if (val === '') {
  811. return false;
  812. }
  813. //get the decimal and thousands separator from numbro.cultureData
  814. try {
  815. //check if the culture is understood by numbro. if not, default it to current culture
  816. cultureData = numbro.cultureData(culture);
  817. } catch (e) {
  818. cultureData = numbro.cultureData(numbro.culture());
  819. }
  820. //setup the delimiters and currency symbol based on culture
  821. _currSymbol = cultureData.currency.symbol;
  822. _abbrObj = cultureData.abbreviations;
  823. _decimalSep = cultureData.delimiters.decimal;
  824. if (cultureData.delimiters.thousands === '.') {
  825. _thousandSep = '\\.';
  826. } else {
  827. _thousandSep = cultureData.delimiters.thousands;
  828. }
  829. // validating currency symbol
  830. temp = val.match(/^[^\d\.\,]+/);
  831. if (temp !== null) {
  832. val = val.substr(1);
  833. if (temp[0] !== _currSymbol) {
  834. return false;
  835. }
  836. }
  837. //validating abbreviation symbol
  838. temp = val.match(/[^\d]+$/);
  839. if (temp !== null) {
  840. val = val.slice(0, -1);
  841. if (temp[0] !== _abbrObj.thousand && temp[0] !== _abbrObj.million &&
  842. temp[0] !== _abbrObj.billion && temp[0] !== _abbrObj.trillion) {
  843. return false;
  844. }
  845. }
  846. _thousandRegEx = new RegExp(_thousandSep + '{2}');
  847. if (!val.match(/[^\d.,]/g)) {
  848. _valArray = val.split(_decimalSep);
  849. if (_valArray.length > 2) {
  850. return false;
  851. } else {
  852. if (_valArray.length < 2) {
  853. return ( !! _valArray[0].match(/^\d+.*\d$/) && !_valArray[0].match(_thousandRegEx));
  854. } else {
  855. if (_valArray[0] === '') {
  856. // for values without leading zero eg. .984
  857. return (!_valArray[0].match(_thousandRegEx) &&
  858. !!_valArray[1].match(/^\d+$/));
  859. } else if (_valArray[0].length === 1) {
  860. return ( !! _valArray[0].match(/^\d+$/) &&
  861. !_valArray[0].match(_thousandRegEx) &&
  862. !! _valArray[1].match(/^\d+$/));
  863. } else {
  864. return ( !! _valArray[0].match(/^\d+.*\d$/) &&
  865. !_valArray[0].match(_thousandRegEx) &&
  866. !! _valArray[1].match(/^\d+$/));
  867. }
  868. }
  869. }
  870. }
  871. return false;
  872. };
  873. /**
  874. * * @deprecated Since in version 1.6.0. It will be deleted in version 2.0
  875. * `loadCulturesInNode` should be used instead.
  876. */
  877. numbro.loadLanguagesInNode = function() {
  878. console.warn('`loadLanguagesInNode` is deprecated since version 1.6.0. Use `loadCulturesInNode` instead');
  879. numbro.loadCulturesInNode();
  880. };
  881. numbro.loadCulturesInNode = function() {
  882. // TODO: Rename the folder in 2.0.0
  883. var cultures = require('./languages');
  884. for(var langLocaleCode in cultures) {
  885. if(langLocaleCode) {
  886. numbro.culture(langLocaleCode, cultures[langLocaleCode]);
  887. }
  888. }
  889. };
  890. /************************************
  891. Helpers
  892. ************************************/
  893. function setCulture(code, values) {
  894. cultures[code] = values;
  895. }
  896. function chooseCulture(code) {
  897. currentCulture = code;
  898. var defaults = cultures[code].defaults;
  899. if (defaults && defaults.format) {
  900. numbro.defaultFormat(defaults.format);
  901. }
  902. if (defaults && defaults.currencyFormat) {
  903. numbro.defaultCurrencyFormat(defaults.currencyFormat);
  904. }
  905. }
  906. function inNodejsRuntime() {
  907. return (typeof process !== 'undefined') &&
  908. (process.browser === undefined) &&
  909. process.title &&
  910. (
  911. process.title.indexOf('node') !== -1 ||
  912. process.title.indexOf('meteor-tool') > 0 ||
  913. process.title === 'grunt' ||
  914. process.title === 'gulp'
  915. ) &&
  916. (typeof require !== 'undefined');
  917. }
  918. /************************************
  919. Floating-point helpers
  920. ************************************/
  921. // The floating-point helper functions and implementation
  922. // borrows heavily from sinful.js: http://guipn.github.io/sinful.js/
  923. /**
  924. * Array.prototype.reduce for browsers that don't support it
  925. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Compatibility
  926. */
  927. if ('function' !== typeof Array.prototype.reduce) {
  928. Array.prototype.reduce = function(callback, optInitialValue) {
  929. if (null === this || 'undefined' === typeof this) {
  930. // At the moment all modern browsers, that support strict mode, have
  931. // native implementation of Array.prototype.reduce. For instance, IE8
  932. // does not support strict mode, so this check is actually useless.
  933. throw new TypeError('Array.prototype.reduce called on null or undefined');
  934. }
  935. if ('function' !== typeof callback) {
  936. throw new TypeError(callback + ' is not a function');
  937. }
  938. var index,
  939. value,
  940. length = this.length >>> 0,
  941. isValueSet = false;
  942. if (1 < arguments.length) {
  943. value = optInitialValue;
  944. isValueSet = true;
  945. }
  946. for (index = 0; length > index; ++index) {
  947. if (this.hasOwnProperty(index)) {
  948. if (isValueSet) {
  949. value = callback(value, this[index], index, this);
  950. } else {
  951. value = this[index];
  952. isValueSet = true;
  953. }
  954. }
  955. }
  956. if (!isValueSet) {
  957. throw new TypeError('Reduce of empty array with no initial value');
  958. }
  959. return value;
  960. };
  961. }
  962. /**
  963. * Computes the multiplier necessary to make x >= 1,
  964. * effectively eliminating miscalculations caused by
  965. * finite precision.
  966. */
  967. function multiplier(x) {
  968. var parts = x.toString().split('.');
  969. if (parts.length < 2) {
  970. return 1;
  971. }
  972. return Math.pow(10, parts[1].length);
  973. }
  974. /**
  975. * Given a variable number of arguments, returns the maximum
  976. * multiplier that must be used to normalize an operation involving
  977. * all of them.
  978. */
  979. function correctionFactor() {
  980. var args = Array.prototype.slice.call(arguments);
  981. return args.reduce(function(prev, next) {
  982. var mp = multiplier(prev),
  983. mn = multiplier(next);
  984. return mp > mn ? mp : mn;
  985. }, -Infinity);
  986. }
  987. /************************************
  988. Numbro Prototype
  989. ************************************/
  990. numbro.fn = Numbro.prototype = {
  991. clone: function() {
  992. return numbro(this);
  993. },
  994. format: function(inputString, roundingFunction) {
  995. return formatNumbro(this,
  996. inputString ? inputString : defaultFormat,
  997. (roundingFunction !== undefined) ? roundingFunction : Math.round
  998. );
  999. },
  1000. formatCurrency: function(inputString, roundingFunction) {
  1001. return formatCurrency(this,
  1002. cultures[currentCulture].currency.symbol,
  1003. inputString ? inputString : defaultCurrencyFormat,
  1004. (roundingFunction !== undefined) ? roundingFunction : Math.round
  1005. );
  1006. },
  1007. formatForeignCurrency: function(currencySymbol, inputString, roundingFunction) {
  1008. return formatForeignCurrency(this,
  1009. currencySymbol,
  1010. inputString ? inputString : defaultCurrencyFormat,
  1011. (roundingFunction !== undefined) ? roundingFunction : Math.round
  1012. );
  1013. },
  1014. unformat: function(inputString) {
  1015. if (typeof inputString === 'number') {
  1016. return inputString;
  1017. } else if (typeof inputString === 'string') {
  1018. var result = unformatNumbro(this, inputString);
  1019. // Any unparseable string (represented as NaN in the result) is
  1020. // converted into undefined.
  1021. return isNaN(result) ? undefined : result;
  1022. } else {
  1023. return undefined;
  1024. }
  1025. },
  1026. binaryByteUnits: function() {
  1027. return formatByteUnits(this._value, bytes.binary.suffixes, bytes.binary.scale).suffix;
  1028. },
  1029. byteUnits: function() {
  1030. return formatByteUnits(this._value, bytes.general.suffixes, bytes.general.scale).suffix;
  1031. },
  1032. decimalByteUnits: function() {
  1033. return formatByteUnits(this._value, bytes.decimal.suffixes, bytes.decimal.scale).suffix;
  1034. },
  1035. value: function() {
  1036. return this._value;
  1037. },
  1038. valueOf: function() {
  1039. return this._value;
  1040. },
  1041. set: function(value) {
  1042. this._value = Number(value);
  1043. return this;
  1044. },
  1045. add: function(value) {
  1046. var corrFactor = correctionFactor.call(null, this._value, value);
  1047. function cback(accum, curr) {
  1048. return accum + corrFactor * curr;
  1049. }
  1050. this._value = [this._value, value].reduce(cback, 0) / corrFactor;
  1051. return this;
  1052. },
  1053. subtract: function(value) {
  1054. var corrFactor = correctionFactor.call(null, this._value, value);
  1055. function cback(accum, curr) {
  1056. return accum - corrFactor * curr;
  1057. }
  1058. this._value = [value].reduce(cback, this._value * corrFactor) / corrFactor;
  1059. return this;
  1060. },
  1061. multiply: function(value) {
  1062. function cback(accum, curr) {
  1063. var corrFactor = correctionFactor(accum, curr),
  1064. result = accum * corrFactor;
  1065. result *= curr * corrFactor;
  1066. result /= corrFactor * corrFactor;
  1067. return result;
  1068. }
  1069. this._value = [this._value, value].reduce(cback, 1);
  1070. return this;
  1071. },
  1072. divide: function(value) {
  1073. function cback(accum, curr) {
  1074. var corrFactor = correctionFactor(accum, curr);
  1075. return (accum * corrFactor) / (curr * corrFactor);
  1076. }
  1077. this._value = [this._value, value].reduce(cback);
  1078. return this;
  1079. },
  1080. difference: function(value) {
  1081. return Math.abs(numbro(this._value).subtract(value).value());
  1082. }
  1083. };
  1084. /************************************
  1085. Exposing Numbro
  1086. ************************************/
  1087. if (inNodejsRuntime()) {
  1088. //Todo: Rename the folder in 2.0.0
  1089. numbro.loadCulturesInNode();
  1090. }
  1091. // CommonJS module is defined
  1092. if (hasModule) {
  1093. module.exports = numbro;
  1094. } else {
  1095. /*global ender:false */
  1096. if (typeof ender === 'undefined') {
  1097. // here, `this` means `window` in the browser, or `global` on the server
  1098. // add `numbro` as a global object via a string identifier,
  1099. // for Closure Compiler 'advanced' mode
  1100. this.numbro = numbro;
  1101. }
  1102. /*global define:false */
  1103. if (typeof define === 'function' && define.amd) {
  1104. define([], function() {
  1105. return numbro;
  1106. });
  1107. }
  1108. }
  1109. }.call(typeof window === 'undefined' ? this : window));