numbro.js 43 KB

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