globalize.number.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275
  1. /**
  2. * Globalize v1.0.1
  3. *
  4. * http://github.com/jquery/globalize
  5. *
  6. * Copyright jQuery Foundation and other contributors
  7. * Released under the MIT license
  8. * http://jquery.org/license
  9. *
  10. * Date: 2016-01-20T16:57Z
  11. */
  12. /*!
  13. * Globalize v1.0.1 2016-01-20T16:57Z Released under the MIT license
  14. * http://git.io/TrdQbw
  15. */
  16. (function( root, factory ) {
  17. // UMD returnExports
  18. if ( typeof define === "function" && define.amd ) {
  19. // AMD
  20. define([
  21. "cldr",
  22. "../globalize",
  23. "cldr/event",
  24. "cldr/supplemental"
  25. ], factory );
  26. } else if ( typeof exports === "object" ) {
  27. // Node, CommonJS
  28. module.exports = factory( require( "cldrjs" ), require( "globalize" ) );
  29. } else {
  30. // Global
  31. factory( root.Cldr, root.Globalize );
  32. }
  33. }(this, function( Cldr, Globalize ) {
  34. var createError = Globalize._createError,
  35. objectExtend = Globalize._objectExtend,
  36. regexpEscape = Globalize._regexpEscape,
  37. stringPad = Globalize._stringPad,
  38. validateCldr = Globalize._validateCldr,
  39. validateDefaultLocale = Globalize._validateDefaultLocale,
  40. validateParameterPresence = Globalize._validateParameterPresence,
  41. validateParameterRange = Globalize._validateParameterRange,
  42. validateParameterType = Globalize._validateParameterType,
  43. validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject;
  44. var createErrorUnsupportedFeature = function( feature ) {
  45. return createError( "E_UNSUPPORTED", "Unsupported {feature}.", {
  46. feature: feature
  47. });
  48. };
  49. var validateParameterTypeNumber = function( value, name ) {
  50. validateParameterType(
  51. value,
  52. name,
  53. value === undefined || typeof value === "number",
  54. "Number"
  55. );
  56. };
  57. var validateParameterTypeString = function( value, name ) {
  58. validateParameterType(
  59. value,
  60. name,
  61. value === undefined || typeof value === "string",
  62. "a string"
  63. );
  64. };
  65. /**
  66. * goupingSeparator( number, primaryGroupingSize, secondaryGroupingSize )
  67. *
  68. * @number [Number].
  69. *
  70. * @primaryGroupingSize [Number]
  71. *
  72. * @secondaryGroupingSize [Number]
  73. *
  74. * Return the formatted number with group separator.
  75. */
  76. var numberFormatGroupingSeparator = function( number, primaryGroupingSize, secondaryGroupingSize ) {
  77. var index,
  78. currentGroupingSize = primaryGroupingSize,
  79. ret = "",
  80. sep = ",",
  81. switchToSecondary = secondaryGroupingSize ? true : false;
  82. number = String( number ).split( "." );
  83. index = number[ 0 ].length;
  84. while ( index > currentGroupingSize ) {
  85. ret = number[ 0 ].slice( index - currentGroupingSize, index ) +
  86. ( ret.length ? sep : "" ) + ret;
  87. index -= currentGroupingSize;
  88. if ( switchToSecondary ) {
  89. currentGroupingSize = secondaryGroupingSize;
  90. switchToSecondary = false;
  91. }
  92. }
  93. number[ 0 ] = number[ 0 ].slice( 0, index ) + ( ret.length ? sep : "" ) + ret;
  94. return number.join( "." );
  95. };
  96. /**
  97. * integerFractionDigits( number, minimumIntegerDigits, minimumFractionDigits,
  98. * maximumFractionDigits, round, roundIncrement )
  99. *
  100. * @number [Number]
  101. *
  102. * @minimumIntegerDigits [Number]
  103. *
  104. * @minimumFractionDigits [Number]
  105. *
  106. * @maximumFractionDigits [Number]
  107. *
  108. * @round [Function]
  109. *
  110. * @roundIncrement [Function]
  111. *
  112. * Return the formatted integer and fraction digits.
  113. */
  114. var numberFormatIntegerFractionDigits = function( number, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, round,
  115. roundIncrement ) {
  116. // Fraction
  117. if ( maximumFractionDigits ) {
  118. // Rounding
  119. if ( roundIncrement ) {
  120. number = round( number, roundIncrement );
  121. // Maximum fraction digits
  122. } else {
  123. number = round( number, { exponent: -maximumFractionDigits } );
  124. }
  125. // Minimum fraction digits
  126. if ( minimumFractionDigits ) {
  127. number = String( number ).split( "." );
  128. number[ 1 ] = stringPad( number[ 1 ] || "", minimumFractionDigits, true );
  129. number = number.join( "." );
  130. }
  131. } else {
  132. number = round( number );
  133. }
  134. number = String( number );
  135. // Minimum integer digits
  136. if ( minimumIntegerDigits ) {
  137. number = number.split( "." );
  138. number[ 0 ] = stringPad( number[ 0 ], minimumIntegerDigits );
  139. number = number.join( "." );
  140. }
  141. return number;
  142. };
  143. /**
  144. * toPrecision( number, precision, round )
  145. *
  146. * @number (Number)
  147. *
  148. * @precision (Number) significant figures precision (not decimal precision).
  149. *
  150. * @round (Function)
  151. *
  152. * Return number.toPrecision( precision ) using the given round function.
  153. */
  154. var numberToPrecision = function( number, precision, round ) {
  155. var roundOrder;
  156. // Get number at two extra significant figure precision.
  157. number = number.toPrecision( precision + 2 );
  158. // Then, round it to the required significant figure precision.
  159. roundOrder = Math.ceil( Math.log( Math.abs( number ) ) / Math.log( 10 ) );
  160. roundOrder -= precision;
  161. return round( number, { exponent: roundOrder } );
  162. };
  163. /**
  164. * toPrecision( number, minimumSignificantDigits, maximumSignificantDigits, round )
  165. *
  166. * @number [Number]
  167. *
  168. * @minimumSignificantDigits [Number]
  169. *
  170. * @maximumSignificantDigits [Number]
  171. *
  172. * @round [Function]
  173. *
  174. * Return the formatted significant digits number.
  175. */
  176. var numberFormatSignificantDigits = function( number, minimumSignificantDigits, maximumSignificantDigits, round ) {
  177. var atMinimum, atMaximum;
  178. // Sanity check.
  179. if ( minimumSignificantDigits > maximumSignificantDigits ) {
  180. maximumSignificantDigits = minimumSignificantDigits;
  181. }
  182. atMinimum = numberToPrecision( number, minimumSignificantDigits, round );
  183. atMaximum = numberToPrecision( number, maximumSignificantDigits, round );
  184. // Use atMaximum only if it has more significant digits than atMinimum.
  185. number = +atMinimum === +atMaximum ? atMinimum : atMaximum;
  186. // Expand integer numbers, eg. 123e5 to 12300.
  187. number = ( +number ).toString( 10 );
  188. if ( ( /e/ ).test( number ) ) {
  189. throw createErrorUnsupportedFeature({
  190. feature: "integers out of (1e21, 1e-7)"
  191. });
  192. }
  193. // Add trailing zeros if necessary.
  194. if ( minimumSignificantDigits - number.replace( /^0+|\./g, "" ).length > 0 ) {
  195. number = number.split( "." );
  196. number[ 1 ] = stringPad( number[ 1 ] || "", minimumSignificantDigits - number[ 0 ].replace( /^0+/, "" ).length, true );
  197. number = number.join( "." );
  198. }
  199. return number;
  200. };
  201. /**
  202. * format( number, properties )
  203. *
  204. * @number [Number].
  205. *
  206. * @properties [Object] Output of number/format-properties.
  207. *
  208. * Return the formatted number.
  209. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
  210. */
  211. var numberFormat = function( number, properties ) {
  212. var infinitySymbol, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits,
  213. minimumIntegerDigits, minimumSignificantDigits, nanSymbol, nuDigitsMap, padding, prefix,
  214. primaryGroupingSize, pattern, ret, round, roundIncrement, secondaryGroupingSize, suffix,
  215. symbolMap;
  216. padding = properties[ 1 ];
  217. minimumIntegerDigits = properties[ 2 ];
  218. minimumFractionDigits = properties[ 3 ];
  219. maximumFractionDigits = properties[ 4 ];
  220. minimumSignificantDigits = properties[ 5 ];
  221. maximumSignificantDigits = properties[ 6 ];
  222. roundIncrement = properties[ 7 ];
  223. primaryGroupingSize = properties[ 8 ];
  224. secondaryGroupingSize = properties[ 9 ];
  225. round = properties[ 15 ];
  226. infinitySymbol = properties[ 16 ];
  227. nanSymbol = properties[ 17 ];
  228. symbolMap = properties[ 18 ];
  229. nuDigitsMap = properties[ 19 ];
  230. // NaN
  231. if ( isNaN( number ) ) {
  232. return nanSymbol;
  233. }
  234. if ( number < 0 ) {
  235. pattern = properties[ 12 ];
  236. prefix = properties[ 13 ];
  237. suffix = properties[ 14 ];
  238. } else {
  239. pattern = properties[ 11 ];
  240. prefix = properties[ 0 ];
  241. suffix = properties[ 10 ];
  242. }
  243. // Infinity
  244. if ( !isFinite( number ) ) {
  245. return prefix + infinitySymbol + suffix;
  246. }
  247. ret = prefix;
  248. // Percent
  249. if ( pattern.indexOf( "%" ) !== -1 ) {
  250. number *= 100;
  251. // Per mille
  252. } else if ( pattern.indexOf( "\u2030" ) !== -1 ) {
  253. number *= 1000;
  254. }
  255. // Significant digit format
  256. if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) {
  257. number = numberFormatSignificantDigits( number, minimumSignificantDigits,
  258. maximumSignificantDigits, round );
  259. // Integer and fractional format
  260. } else {
  261. number = numberFormatIntegerFractionDigits( number, minimumIntegerDigits,
  262. minimumFractionDigits, maximumFractionDigits, round, roundIncrement );
  263. }
  264. // Remove the possible number minus sign
  265. number = number.replace( /^-/, "" );
  266. // Grouping separators
  267. if ( primaryGroupingSize ) {
  268. number = numberFormatGroupingSeparator( number, primaryGroupingSize,
  269. secondaryGroupingSize );
  270. }
  271. ret += number;
  272. // Scientific notation
  273. // TODO implement here
  274. // Padding/'([^']|'')+'|''|[.,\-+E%\u2030]/g
  275. // TODO implement here
  276. ret += suffix;
  277. return ret.replace( /('([^']|'')+'|'')|./g, function( character, literal ) {
  278. // Literals
  279. if ( literal ) {
  280. literal = literal.replace( /''/, "'" );
  281. if ( literal.length > 2 ) {
  282. literal = literal.slice( 1, -1 );
  283. }
  284. return literal;
  285. }
  286. // Symbols
  287. character = character.replace( /[.,\-+E%\u2030]/, function( symbol ) {
  288. return symbolMap[ symbol ];
  289. });
  290. // Numbering system
  291. if ( nuDigitsMap ) {
  292. character = character.replace( /[0-9]/, function( digit ) {
  293. return nuDigitsMap[ +digit ];
  294. });
  295. }
  296. return character;
  297. });
  298. };
  299. /**
  300. * NumberingSystem( cldr )
  301. *
  302. * - http://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
  303. * - http://cldr.unicode.org/index/bcp47-extension
  304. * - http://www.unicode.org/reports/tr35/#u_Extension
  305. */
  306. var numberNumberingSystem = function( cldr ) {
  307. var nu = cldr.attributes[ "u-nu" ];
  308. if ( nu ) {
  309. if ( nu === "traditio" ) {
  310. nu = "traditional";
  311. }
  312. if ( [ "native", "traditional", "finance" ].indexOf( nu ) !== -1 ) {
  313. // Unicode locale extension `u-nu` is set using either (native, traditional or
  314. // finance). So, lookup the respective locale's numberingSystem and return it.
  315. return cldr.main([ "numbers/otherNumberingSystems", nu ]);
  316. }
  317. // Unicode locale extension `u-nu` is set with an explicit numberingSystem. Return it.
  318. return nu;
  319. }
  320. // Return the default numberingSystem.
  321. return cldr.main( "numbers/defaultNumberingSystem" );
  322. };
  323. /**
  324. * nuMap( cldr )
  325. *
  326. * @cldr [Cldr instance].
  327. *
  328. * Return digits map if numbering system is different than `latn`.
  329. */
  330. var numberNumberingSystemDigitsMap = function( cldr ) {
  331. var aux,
  332. nu = numberNumberingSystem( cldr );
  333. if ( nu === "latn" ) {
  334. return;
  335. }
  336. aux = cldr.supplemental([ "numberingSystems", nu ]);
  337. if ( aux._type !== "numeric" ) {
  338. throw createErrorUnsupportedFeature( "`" + aux._type + "` numbering system" );
  339. }
  340. return aux._digits;
  341. };
  342. /**
  343. * EBNF representation:
  344. *
  345. * number_pattern_re = prefix?
  346. * padding?
  347. * (integer_fraction_pattern | significant_pattern)
  348. * scientific_notation?
  349. * suffix?
  350. *
  351. * prefix = non_number_stuff
  352. *
  353. * padding = "*" regexp(.)
  354. *
  355. * integer_fraction_pattern = integer_pattern
  356. * fraction_pattern?
  357. *
  358. * integer_pattern = regexp([#,]*[0,]*0+)
  359. *
  360. * fraction_pattern = "." regexp(0*[0-9]*#*)
  361. *
  362. * significant_pattern = regexp([#,]*@+#*)
  363. *
  364. * scientific_notation = regexp(E\+?0+)
  365. *
  366. * suffix = non_number_stuff
  367. *
  368. * non_number_stuff = regexp(('[^']+'|''|[^*#@0,.E])*)
  369. *
  370. *
  371. * Regexp groups:
  372. *
  373. * 0: number_pattern_re
  374. * 1: prefix
  375. * 2: -
  376. * 3: padding
  377. * 4: (integer_fraction_pattern | significant_pattern)
  378. * 5: integer_fraction_pattern
  379. * 6: integer_pattern
  380. * 7: fraction_pattern
  381. * 8: significant_pattern
  382. * 9: scientific_notation
  383. * 10: suffix
  384. * 11: -
  385. */
  386. var numberPatternRe = ( /^(('[^']+'|''|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(('[^']+'|''|[^*#@0,.E])*)$/ );
  387. /**
  388. * format( number, pattern )
  389. *
  390. * @number [Number].
  391. *
  392. * @pattern [String] raw pattern for numbers.
  393. *
  394. * Return the formatted number.
  395. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
  396. */
  397. var numberPatternProperties = function( pattern ) {
  398. var aux1, aux2, fractionPattern, integerFractionOrSignificantPattern, integerPattern,
  399. maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits,
  400. minimumIntegerDigits, minimumSignificantDigits, padding, prefix, primaryGroupingSize,
  401. roundIncrement, scientificNotation, secondaryGroupingSize, significantPattern, suffix;
  402. pattern = pattern.match( numberPatternRe );
  403. if ( !pattern ) {
  404. throw new Error( "Invalid pattern: " + pattern );
  405. }
  406. prefix = pattern[ 1 ];
  407. padding = pattern[ 3 ];
  408. integerFractionOrSignificantPattern = pattern[ 4 ];
  409. significantPattern = pattern[ 8 ];
  410. scientificNotation = pattern[ 9 ];
  411. suffix = pattern[ 10 ];
  412. // Significant digit format
  413. if ( significantPattern ) {
  414. significantPattern.replace( /(@+)(#*)/, function( match, minimumSignificantDigitsMatch, maximumSignificantDigitsMatch ) {
  415. minimumSignificantDigits = minimumSignificantDigitsMatch.length;
  416. maximumSignificantDigits = minimumSignificantDigits +
  417. maximumSignificantDigitsMatch.length;
  418. });
  419. // Integer and fractional format
  420. } else {
  421. fractionPattern = pattern[ 7 ];
  422. integerPattern = pattern[ 6 ];
  423. if ( fractionPattern ) {
  424. // Minimum fraction digits, and rounding.
  425. fractionPattern.replace( /[0-9]+/, function( match ) {
  426. minimumFractionDigits = match;
  427. });
  428. if ( minimumFractionDigits ) {
  429. roundIncrement = +( "0." + minimumFractionDigits );
  430. minimumFractionDigits = minimumFractionDigits.length;
  431. } else {
  432. minimumFractionDigits = 0;
  433. }
  434. // Maximum fraction digits
  435. // 1: ignore decimal character
  436. maximumFractionDigits = fractionPattern.length - 1 /* 1 */;
  437. }
  438. // Minimum integer digits
  439. integerPattern.replace( /0+$/, function( match ) {
  440. minimumIntegerDigits = match.length;
  441. });
  442. }
  443. // Scientific notation
  444. if ( scientificNotation ) {
  445. throw createErrorUnsupportedFeature({
  446. feature: "scientific notation (not implemented)"
  447. });
  448. }
  449. // Padding
  450. if ( padding ) {
  451. throw createErrorUnsupportedFeature({
  452. feature: "padding (not implemented)"
  453. });
  454. }
  455. // Grouping
  456. if ( ( aux1 = integerFractionOrSignificantPattern.lastIndexOf( "," ) ) !== -1 ) {
  457. // Primary grouping size is the interval between the last group separator and the end of
  458. // the integer (or the end of the significant pattern).
  459. aux2 = integerFractionOrSignificantPattern.split( "." )[ 0 ];
  460. primaryGroupingSize = aux2.length - aux1 - 1;
  461. // Secondary grouping size is the interval between the last two group separators.
  462. if ( ( aux2 = integerFractionOrSignificantPattern.lastIndexOf( ",", aux1 - 1 ) ) !== -1 ) {
  463. secondaryGroupingSize = aux1 - 1 - aux2;
  464. }
  465. }
  466. // Return:
  467. // 0: @prefix String
  468. // 1: @padding Array [ <character>, <count> ] TODO
  469. // 2: @minimumIntegerDigits non-negative integer Number value indicating the minimum integer
  470. // digits to be used. Numbers will be padded with leading zeroes if necessary.
  471. // 3: @minimumFractionDigits and
  472. // 4: @maximumFractionDigits are non-negative integer Number values indicating the minimum and
  473. // maximum fraction digits to be used. Numbers will be rounded or padded with trailing
  474. // zeroes if necessary.
  475. // 5: @minimumSignificantDigits and
  476. // 6: @maximumSignificantDigits are positive integer Number values indicating the minimum and
  477. // maximum fraction digits to be shown. Either none or both of these properties are
  478. // present; if they are, they override minimum and maximum integer and fraction digits
  479. // – the formatter uses however many integer and fraction digits are required to display
  480. // the specified number of significant digits.
  481. // 7: @roundIncrement Decimal round increment or null
  482. // 8: @primaryGroupingSize
  483. // 9: @secondaryGroupingSize
  484. // 10: @suffix String
  485. return [
  486. prefix,
  487. padding,
  488. minimumIntegerDigits,
  489. minimumFractionDigits,
  490. maximumFractionDigits,
  491. minimumSignificantDigits,
  492. maximumSignificantDigits,
  493. roundIncrement,
  494. primaryGroupingSize,
  495. secondaryGroupingSize,
  496. suffix
  497. ];
  498. };
  499. /**
  500. * Symbol( name, cldr )
  501. *
  502. * @name [String] Symbol name.
  503. *
  504. * @cldr [Cldr instance].
  505. *
  506. * Return the localized symbol given its name.
  507. */
  508. var numberSymbol = function( name, cldr ) {
  509. return cldr.main([
  510. "numbers/symbols-numberSystem-" + numberNumberingSystem( cldr ),
  511. name
  512. ]);
  513. };
  514. var numberSymbolName = {
  515. ".": "decimal",
  516. ",": "group",
  517. "%": "percentSign",
  518. "+": "plusSign",
  519. "-": "minusSign",
  520. "E": "exponential",
  521. "\u2030": "perMille"
  522. };
  523. /**
  524. * symbolMap( cldr )
  525. *
  526. * @cldr [Cldr instance].
  527. *
  528. * Return the (localized symbol, pattern symbol) key value pair, eg. {
  529. * ".": "٫",
  530. * ",": "٬",
  531. * "%": "٪",
  532. * ...
  533. * };
  534. */
  535. var numberSymbolMap = function( cldr ) {
  536. var symbol,
  537. symbolMap = {};
  538. for ( symbol in numberSymbolName ) {
  539. symbolMap[ symbol ] = numberSymbol( numberSymbolName[ symbol ], cldr );
  540. }
  541. return symbolMap;
  542. };
  543. var numberTruncate = function( value ) {
  544. if ( isNaN( value ) ) {
  545. return NaN;
  546. }
  547. return Math[ value < 0 ? "ceil" : "floor" ]( value );
  548. };
  549. /**
  550. * round( method )
  551. *
  552. * @method [String] with either "round", "ceil", "floor", or "truncate".
  553. *
  554. * Return function( value, incrementOrExp ):
  555. *
  556. * @value [Number] eg. 123.45.
  557. *
  558. * @incrementOrExp [Number] optional, eg. 0.1; or
  559. * [Object] Either { increment: <value> } or { exponent: <value> }
  560. *
  561. * Return the rounded number, eg:
  562. * - round( "round" )( 123.45 ): 123;
  563. * - round( "ceil" )( 123.45 ): 124;
  564. * - round( "floor" )( 123.45 ): 123;
  565. * - round( "truncate" )( 123.45 ): 123;
  566. * - round( "round" )( 123.45, 0.1 ): 123.5;
  567. * - round( "round" )( 123.45, 10 ): 120;
  568. *
  569. * Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
  570. * Ref: #376
  571. */
  572. var numberRound = function( method ) {
  573. method = method || "round";
  574. method = method === "truncate" ? numberTruncate : Math[ method ];
  575. return function( value, incrementOrExp ) {
  576. var exp, increment;
  577. value = +value;
  578. // If the value is not a number, return NaN.
  579. if ( isNaN( value ) ) {
  580. return NaN;
  581. }
  582. // Exponent given.
  583. if ( typeof incrementOrExp === "object" && incrementOrExp.exponent ) {
  584. exp = +incrementOrExp.exponent;
  585. increment = 1;
  586. if ( exp === 0 ) {
  587. return method( value );
  588. }
  589. // If the exp is not an integer, return NaN.
  590. if ( !( typeof exp === "number" && exp % 1 === 0 ) ) {
  591. return NaN;
  592. }
  593. // Increment given.
  594. } else {
  595. increment = +incrementOrExp || 1;
  596. if ( increment === 1 ) {
  597. return method( value );
  598. }
  599. // If the increment is not a number, return NaN.
  600. if ( isNaN( increment ) ) {
  601. return NaN;
  602. }
  603. increment = increment.toExponential().split( "e" );
  604. exp = +increment[ 1 ];
  605. increment = +increment[ 0 ];
  606. }
  607. // Shift & Round
  608. value = value.toString().split( "e" );
  609. value[ 0 ] = +value[ 0 ] / increment;
  610. value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] - exp ) : -exp;
  611. value = method( +( value[ 0 ] + "e" + value[ 1 ] ) );
  612. // Shift back
  613. value = value.toString().split( "e" );
  614. value[ 0 ] = +value[ 0 ] * increment;
  615. value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] + exp ) : exp;
  616. return +( value[ 0 ] + "e" + value[ 1 ] );
  617. };
  618. };
  619. /**
  620. * formatProperties( pattern, cldr [, options] )
  621. *
  622. * @pattern [String] raw pattern for numbers.
  623. *
  624. * @cldr [Cldr instance].
  625. *
  626. * @options [Object]:
  627. * - minimumIntegerDigits [Number]
  628. * - minimumFractionDigits, maximumFractionDigits [Number]
  629. * - minimumSignificantDigits, maximumSignificantDigits [Number]
  630. * - round [String] "ceil", "floor", "round" (default), or "truncate".
  631. * - useGrouping [Boolean] default true.
  632. *
  633. * Return the processed properties that will be used in number/format.
  634. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
  635. */
  636. var numberFormatProperties = function( pattern, cldr, options ) {
  637. var negativePattern, negativePrefix, negativeProperties, negativeSuffix, positivePattern,
  638. properties;
  639. function getOptions( attribute, propertyIndex ) {
  640. if ( attribute in options ) {
  641. properties[ propertyIndex ] = options[ attribute ];
  642. }
  643. }
  644. options = options || {};
  645. pattern = pattern.split( ";" );
  646. positivePattern = pattern[ 0 ];
  647. negativePattern = pattern[ 1 ] || "-" + positivePattern;
  648. negativeProperties = numberPatternProperties( negativePattern );
  649. negativePrefix = negativeProperties[ 0 ];
  650. negativeSuffix = negativeProperties[ 10 ];
  651. properties = numberPatternProperties( positivePattern ).concat([
  652. positivePattern,
  653. negativePrefix + positivePattern + negativeSuffix,
  654. negativePrefix,
  655. negativeSuffix,
  656. numberRound( options.round ),
  657. numberSymbol( "infinity", cldr ),
  658. numberSymbol( "nan", cldr ),
  659. numberSymbolMap( cldr ),
  660. numberNumberingSystemDigitsMap( cldr )
  661. ]);
  662. getOptions( "minimumIntegerDigits", 2 );
  663. getOptions( "minimumFractionDigits", 3 );
  664. getOptions( "maximumFractionDigits", 4 );
  665. getOptions( "minimumSignificantDigits", 5 );
  666. getOptions( "maximumSignificantDigits", 6 );
  667. // Grouping separators
  668. if ( options.useGrouping === false ) {
  669. properties[ 8 ] = null;
  670. }
  671. // Normalize number of digits if only one of either minimumFractionDigits or
  672. // maximumFractionDigits is passed in as an option
  673. if ( "minimumFractionDigits" in options && !( "maximumFractionDigits" in options ) ) {
  674. // maximumFractionDigits = Math.max( minimumFractionDigits, maximumFractionDigits );
  675. properties[ 4 ] = Math.max( properties[ 3 ], properties[ 4 ] );
  676. } else if ( !( "minimumFractionDigits" in options ) &&
  677. "maximumFractionDigits" in options ) {
  678. // minimumFractionDigits = Math.min( minimumFractionDigits, maximumFractionDigits );
  679. properties[ 3 ] = Math.min( properties[ 3 ], properties[ 4 ] );
  680. }
  681. // Return:
  682. // 0-10: see number/pattern-properties.
  683. // 11: @positivePattern [String] Positive pattern.
  684. // 12: @negativePattern [String] Negative pattern.
  685. // 13: @negativePrefix [String] Negative prefix.
  686. // 14: @negativeSuffix [String] Negative suffix.
  687. // 15: @round [Function] Round function.
  688. // 16: @infinitySymbol [String] Infinity symbol.
  689. // 17: @nanSymbol [String] NaN symbol.
  690. // 18: @symbolMap [Object] A bunch of other symbols.
  691. // 19: @nuDigitsMap [Array] Digits map if numbering system is different than `latn`.
  692. return properties;
  693. };
  694. /**
  695. * EBNF representation:
  696. *
  697. * number_pattern_re = prefix_including_padding?
  698. * number
  699. * scientific_notation?
  700. * suffix?
  701. *
  702. * number = integer_including_group_separator fraction_including_decimal_separator
  703. *
  704. * integer_including_group_separator =
  705. * regexp([0-9,]*[0-9]+)
  706. *
  707. * fraction_including_decimal_separator =
  708. * regexp((\.[0-9]+)?)
  709. * prefix_including_padding = non_number_stuff
  710. *
  711. * scientific_notation = regexp(E[+-]?[0-9]+)
  712. *
  713. * suffix = non_number_stuff
  714. *
  715. * non_number_stuff = regexp([^0-9]*)
  716. *
  717. *
  718. * Regexp groups:
  719. *
  720. * 0: number_pattern_re
  721. * 1: prefix
  722. * 2: integer_including_group_separator fraction_including_decimal_separator
  723. * 3: integer_including_group_separator
  724. * 4: fraction_including_decimal_separator
  725. * 5: scientific_notation
  726. * 6: suffix
  727. */
  728. var numberNumberRe = ( /^([^0-9]*)(([0-9,]*[0-9]+)(\.[0-9]+)?)(E[+-]?[0-9]+)?([^0-9]*)$/ );
  729. /**
  730. * parse( value, properties )
  731. *
  732. * @value [String].
  733. *
  734. * @properties [Object] Parser properties is a reduced pre-processed cldr
  735. * data set returned by numberParserProperties().
  736. *
  737. * Return the parsed Number (including Infinity) or NaN when value is invalid.
  738. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
  739. */
  740. var numberParse = function( value, properties ) {
  741. var aux, infinitySymbol, invertedNuDigitsMap, invertedSymbolMap, localizedDigitRe,
  742. localizedSymbolsRe, negativePrefix, negativeSuffix, number, prefix, suffix;
  743. infinitySymbol = properties[ 0 ];
  744. invertedSymbolMap = properties[ 1 ];
  745. negativePrefix = properties[ 2 ];
  746. negativeSuffix = properties[ 3 ];
  747. invertedNuDigitsMap = properties[ 4 ];
  748. // Infinite number.
  749. if ( aux = value.match( infinitySymbol ) ) {
  750. number = Infinity;
  751. prefix = value.slice( 0, aux.length );
  752. suffix = value.slice( aux.length + 1 );
  753. // Finite number.
  754. } else {
  755. // TODO: Create it during setup, i.e., make it a property.
  756. localizedSymbolsRe = new RegExp(
  757. Object.keys( invertedSymbolMap ).map(function( localizedSymbol ) {
  758. return regexpEscape( localizedSymbol );
  759. }).join( "|" ),
  760. "g"
  761. );
  762. // Reverse localized symbols.
  763. value = value.replace( localizedSymbolsRe, function( localizedSymbol ) {
  764. return invertedSymbolMap[ localizedSymbol ];
  765. });
  766. // Reverse localized numbering system.
  767. if ( invertedNuDigitsMap ) {
  768. // TODO: Create it during setup, i.e., make it a property.
  769. localizedDigitRe = new RegExp(
  770. Object.keys( invertedNuDigitsMap ).map(function( localizedDigit ) {
  771. return regexpEscape( localizedDigit );
  772. }).join( "|" ),
  773. "g"
  774. );
  775. value = value.replace( localizedDigitRe, function( localizedDigit ) {
  776. return invertedNuDigitsMap[ localizedDigit ];
  777. });
  778. }
  779. // Add padding zero to leading decimal.
  780. if ( value.charAt( 0 ) === "." ) {
  781. value = "0" + value;
  782. }
  783. // Is it a valid number?
  784. value = value.match( numberNumberRe );
  785. if ( !value ) {
  786. // Invalid number.
  787. return NaN;
  788. }
  789. prefix = value[ 1 ];
  790. suffix = value[ 6 ];
  791. // Remove grouping separators.
  792. number = value[ 2 ].replace( /,/g, "" );
  793. // Scientific notation
  794. if ( value[ 5 ] ) {
  795. number += value[ 5 ];
  796. }
  797. number = +number;
  798. // Is it a valid number?
  799. if ( isNaN( number ) ) {
  800. // Invalid number.
  801. return NaN;
  802. }
  803. // Percent
  804. if ( value[ 0 ].indexOf( "%" ) !== -1 ) {
  805. number /= 100;
  806. suffix = suffix.replace( "%", "" );
  807. // Per mille
  808. } else if ( value[ 0 ].indexOf( "\u2030" ) !== -1 ) {
  809. number /= 1000;
  810. suffix = suffix.replace( "\u2030", "" );
  811. }
  812. }
  813. // Negative number
  814. // "If there is an explicit negative subpattern, it serves only to specify the negative prefix
  815. // and suffix. If there is no explicit negative subpattern, the negative subpattern is the
  816. // localized minus sign prefixed to the positive subpattern" UTS#35
  817. if ( prefix === negativePrefix && suffix === negativeSuffix ) {
  818. number *= -1;
  819. }
  820. return number;
  821. };
  822. /**
  823. * symbolMap( cldr )
  824. *
  825. * @cldr [Cldr instance].
  826. *
  827. * Return the (localized symbol, pattern symbol) key value pair, eg. {
  828. * "٫": ".",
  829. * "٬": ",",
  830. * "٪": "%",
  831. * ...
  832. * };
  833. */
  834. var numberSymbolInvertedMap = function( cldr ) {
  835. var symbol,
  836. symbolMap = {};
  837. for ( symbol in numberSymbolName ) {
  838. symbolMap[ numberSymbol( numberSymbolName[ symbol ], cldr ) ] = symbol;
  839. }
  840. return symbolMap;
  841. };
  842. /**
  843. * parseProperties( pattern, cldr )
  844. *
  845. * @pattern [String] raw pattern for numbers.
  846. *
  847. * @cldr [Cldr instance].
  848. *
  849. * Return parser properties, used to feed parser function.
  850. */
  851. var numberParseProperties = function( pattern, cldr ) {
  852. var invertedNuDigitsMap, invertedNuDigitsMapSanityCheck, negativePattern, negativeProperties,
  853. nuDigitsMap = numberNumberingSystemDigitsMap( cldr );
  854. pattern = pattern.split( ";" );
  855. negativePattern = pattern[ 1 ] || "-" + pattern[ 0 ];
  856. negativeProperties = numberPatternProperties( negativePattern );
  857. if ( nuDigitsMap ) {
  858. invertedNuDigitsMap = nuDigitsMap.split( "" ).reduce(function( object, localizedDigit, i ) {
  859. object[ localizedDigit ] = String( i );
  860. return object;
  861. }, {} );
  862. invertedNuDigitsMapSanityCheck = "0123456789".split( "" ).reduce(function( object, digit ) {
  863. object[ digit ] = "invalid";
  864. return object;
  865. }, {} );
  866. invertedNuDigitsMap = objectExtend(
  867. invertedNuDigitsMapSanityCheck,
  868. invertedNuDigitsMap
  869. );
  870. }
  871. // 0: @infinitySymbol [String] Infinity symbol.
  872. // 1: @invertedSymbolMap [Object] Inverted symbol map augmented with sanity check.
  873. // The sanity check prevents permissive parsing, i.e., it prevents symbols that doesn't
  874. // belong to the localized set to pass through. This is obtained with the result of the
  875. // inverted map object overloading symbol name map object (the remaining symbol name
  876. // mappings will invalidate parsing, working as the sanity check).
  877. // 2: @negativePrefix [String] Negative prefix.
  878. // 3: @negativeSuffix [String] Negative suffix with percent or per mille stripped out.
  879. // 4: @invertedNuDigitsMap [Object] Inverted digits map if numbering system is different than
  880. // `latn` augmented with sanity check (similar to invertedSymbolMap).
  881. return [
  882. numberSymbol( "infinity", cldr ),
  883. objectExtend( {}, numberSymbolName, numberSymbolInvertedMap( cldr ) ),
  884. negativeProperties[ 0 ],
  885. negativeProperties[ 10 ].replace( "%", "" ).replace( "\u2030", "" ),
  886. invertedNuDigitsMap
  887. ];
  888. };
  889. /**
  890. * Pattern( style )
  891. *
  892. * @style [String] "decimal" (default) or "percent".
  893. *
  894. * @cldr [Cldr instance].
  895. */
  896. var numberPattern = function( style, cldr ) {
  897. if ( style !== "decimal" && style !== "percent" ) {
  898. throw new Error( "Invalid style" );
  899. }
  900. return cldr.main([
  901. "numbers",
  902. style + "Formats-numberSystem-" + numberNumberingSystem( cldr ),
  903. "standard"
  904. ]);
  905. };
  906. function validateDigits( properties ) {
  907. var minimumIntegerDigits = properties[ 2 ],
  908. minimumFractionDigits = properties[ 3 ],
  909. maximumFractionDigits = properties[ 4 ],
  910. minimumSignificantDigits = properties[ 5 ],
  911. maximumSignificantDigits = properties[ 6 ];
  912. // Validate significant digit format properties
  913. if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) {
  914. validateParameterRange( minimumSignificantDigits, "minimumSignificantDigits", 1, 21 );
  915. validateParameterRange( maximumSignificantDigits, "maximumSignificantDigits",
  916. minimumSignificantDigits, 21 );
  917. } else if ( !isNaN( minimumSignificantDigits ) || !isNaN( maximumSignificantDigits ) ) {
  918. throw new Error( "Neither or both the minimum and maximum significant digits must be " +
  919. "present" );
  920. // Validate integer and fractional format
  921. } else {
  922. validateParameterRange( minimumIntegerDigits, "minimumIntegerDigits", 1, 21 );
  923. validateParameterRange( minimumFractionDigits, "minimumFractionDigits", 0, 20 );
  924. validateParameterRange( maximumFractionDigits, "maximumFractionDigits",
  925. minimumFractionDigits, 20 );
  926. }
  927. }
  928. /**
  929. * .numberFormatter( [options] )
  930. *
  931. * @options [Object]:
  932. * - style: [String] "decimal" (default) or "percent".
  933. * - see also number/format options.
  934. *
  935. * Return a function that formats a number according to the given options and default/instance
  936. * locale.
  937. */
  938. Globalize.numberFormatter =
  939. Globalize.prototype.numberFormatter = function( options ) {
  940. var cldr, pattern, properties;
  941. validateParameterTypePlainObject( options, "options" );
  942. options = options || {};
  943. cldr = this.cldr;
  944. validateDefaultLocale( cldr );
  945. cldr.on( "get", validateCldr );
  946. if ( options.raw ) {
  947. pattern = options.raw;
  948. } else {
  949. pattern = numberPattern( options.style || "decimal", cldr );
  950. }
  951. properties = numberFormatProperties( pattern, cldr, options );
  952. cldr.off( "get", validateCldr );
  953. validateDigits( properties );
  954. return function( value ) {
  955. validateParameterPresence( value, "value" );
  956. validateParameterTypeNumber( value, "value" );
  957. return numberFormat( value, properties );
  958. };
  959. };
  960. /**
  961. * .numberParser( [options] )
  962. *
  963. * @options [Object]:
  964. * - style: [String] "decimal" (default) or "percent".
  965. *
  966. * Return the number parser according to the default/instance locale.
  967. */
  968. Globalize.numberParser =
  969. Globalize.prototype.numberParser = function( options ) {
  970. var cldr, pattern, properties;
  971. validateParameterTypePlainObject( options, "options" );
  972. options = options || {};
  973. cldr = this.cldr;
  974. validateDefaultLocale( cldr );
  975. cldr.on( "get", validateCldr );
  976. if ( options.raw ) {
  977. pattern = options.raw;
  978. } else {
  979. pattern = numberPattern( options.style || "decimal", cldr );
  980. }
  981. properties = numberParseProperties( pattern, cldr );
  982. cldr.off( "get", validateCldr );
  983. return function( value ) {
  984. validateParameterPresence( value, "value" );
  985. validateParameterTypeString( value, "value" );
  986. return numberParse( value, properties );
  987. };
  988. };
  989. /**
  990. * .formatNumber( value [, options] )
  991. *
  992. * @value [Number] number to be formatted.
  993. *
  994. * @options [Object]: see number/format-properties.
  995. *
  996. * Format a number according to the given options and default/instance locale.
  997. */
  998. Globalize.formatNumber =
  999. Globalize.prototype.formatNumber = function( value, options ) {
  1000. validateParameterPresence( value, "value" );
  1001. validateParameterTypeNumber( value, "value" );
  1002. return this.numberFormatter( options )( value );
  1003. };
  1004. /**
  1005. * .parseNumber( value [, options] )
  1006. *
  1007. * @value [String]
  1008. *
  1009. * @options [Object]: See numberParser().
  1010. *
  1011. * Return the parsed Number (including Infinity) or NaN when value is invalid.
  1012. */
  1013. Globalize.parseNumber =
  1014. Globalize.prototype.parseNumber = function( value, options ) {
  1015. validateParameterPresence( value, "value" );
  1016. validateParameterTypeString( value, "value" );
  1017. return this.numberParser( options )( value );
  1018. };
  1019. /**
  1020. * Optimization to avoid duplicating some internal functions across modules.
  1021. */
  1022. Globalize._createErrorUnsupportedFeature = createErrorUnsupportedFeature;
  1023. Globalize._numberNumberingSystem = numberNumberingSystem;
  1024. Globalize._numberPattern = numberPattern;
  1025. Globalize._numberSymbol = numberSymbol;
  1026. Globalize._stringPad = stringPad;
  1027. Globalize._validateParameterTypeNumber = validateParameterTypeNumber;
  1028. Globalize._validateParameterTypeString = validateParameterTypeString;
  1029. return Globalize;
  1030. }));