numberformat.js 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Number format/parse library with locale support.
  16. */
  17. /**
  18. * Namespace for locale number format functions
  19. */
  20. goog.provide('goog.i18n.NumberFormat');
  21. goog.provide('goog.i18n.NumberFormat.CurrencyStyle');
  22. goog.provide('goog.i18n.NumberFormat.Format');
  23. goog.require('goog.asserts');
  24. goog.require('goog.i18n.CompactNumberFormatSymbols');
  25. goog.require('goog.i18n.NumberFormatSymbols');
  26. goog.require('goog.i18n.NumberFormatSymbols_u_nu_latn');
  27. goog.require('goog.i18n.currency');
  28. goog.require('goog.math');
  29. goog.require('goog.string');
  30. /**
  31. * Constructor of NumberFormat.
  32. * @param {number|string} pattern The number that indicates a predefined
  33. * number format pattern.
  34. * @param {string=} opt_currency Optional international currency
  35. * code. This determines the currency code/symbol used in format/parse. If
  36. * not given, the currency code for current locale will be used.
  37. * @param {number=} opt_currencyStyle currency style, value defined in
  38. * goog.i18n.NumberFormat.CurrencyStyle.
  39. * @constructor
  40. */
  41. goog.i18n.NumberFormat = function(pattern, opt_currency, opt_currencyStyle) {
  42. /** @private {string|undefined} */
  43. this.intlCurrencyCode_ = opt_currency;
  44. /** @private {number} */
  45. this.currencyStyle_ =
  46. opt_currencyStyle || goog.i18n.NumberFormat.CurrencyStyle.LOCAL;
  47. /** @private {number} */
  48. this.maximumIntegerDigits_ = 40;
  49. /** @private {number} */
  50. this.minimumIntegerDigits_ = 1;
  51. /** @private {number} */
  52. this.significantDigits_ = 0; // invariant, <= maximumFractionDigits
  53. /** @private {number} */
  54. this.maximumFractionDigits_ = 3; // invariant, >= minFractionDigits
  55. /** @private {number} */
  56. this.minimumFractionDigits_ = 0;
  57. /** @private {number} */
  58. this.minExponentDigits_ = 0;
  59. /** @private {boolean} */
  60. this.useSignForPositiveExponent_ = false;
  61. /**
  62. * Whether to show trailing zeros in the fraction when significantDigits_ is
  63. * positive.
  64. * @private {boolean}
  65. */
  66. this.showTrailingZeros_ = false;
  67. /** @private {string} */
  68. this.positivePrefix_ = '';
  69. /** @private {string} */
  70. this.positiveSuffix_ = '';
  71. /** @private {string} */
  72. this.negativePrefix_ = '-';
  73. /** @private {string} */
  74. this.negativeSuffix_ = '';
  75. // The multiplier for use in percent, per mille, etc.
  76. /** @private {number} */
  77. this.multiplier_ = 1;
  78. /**
  79. * True if the percent/permill sign of the negative pattern is expected.
  80. * @private {!boolean}
  81. */
  82. this.negativePercentSignExpected_ = false;
  83. /**
  84. * The grouping array is used to store the values of each number group
  85. * following left of the decimal place. For example, a number group with
  86. * goog.i18n.NumberFormat('#,##,###') should have [3,2] where 2 is the
  87. * repeated number group following a fixed number grouping of size 3.
  88. * @private {!Array<number>}
  89. */
  90. this.groupingArray_ = [];
  91. /** @private {boolean} */
  92. this.decimalSeparatorAlwaysShown_ = false;
  93. /** @private {boolean} */
  94. this.useExponentialNotation_ = false;
  95. /** @private {goog.i18n.NumberFormat.CompactStyle} */
  96. this.compactStyle_ = goog.i18n.NumberFormat.CompactStyle.NONE;
  97. /**
  98. * The number to base the formatting on when using compact styles, or null
  99. * if formatting should not be based on another number.
  100. * @type {?number}
  101. * @private
  102. */
  103. this.baseFormattingNumber_ = null;
  104. /** @private {string} */
  105. this.pattern_;
  106. if (typeof pattern == 'number') {
  107. this.applyStandardPattern_(pattern);
  108. } else {
  109. this.applyPattern_(pattern);
  110. }
  111. };
  112. /**
  113. * Standard number formatting patterns.
  114. * @enum {number}
  115. */
  116. goog.i18n.NumberFormat.Format = {
  117. DECIMAL: 1,
  118. SCIENTIFIC: 2,
  119. PERCENT: 3,
  120. CURRENCY: 4,
  121. COMPACT_SHORT: 5,
  122. COMPACT_LONG: 6
  123. };
  124. /**
  125. * Currency styles.
  126. * @enum {number}
  127. */
  128. goog.i18n.NumberFormat.CurrencyStyle = {
  129. LOCAL: 0, // currency style as it is used in its circulating country.
  130. PORTABLE: 1, // currency style that differentiate it from other popular ones.
  131. GLOBAL: 2 // currency style that is unique among all currencies.
  132. };
  133. /**
  134. * Compacting styles.
  135. * @enum {number}
  136. */
  137. goog.i18n.NumberFormat.CompactStyle = {
  138. NONE: 0, // Don't compact.
  139. SHORT: 1, // Short compact form, such as 1.2B.
  140. LONG: 2 // Long compact form, such as 1.2 billion.
  141. };
  142. /**
  143. * If the usage of Ascii digits should be enforced.
  144. * @type {boolean}
  145. * @private
  146. */
  147. goog.i18n.NumberFormat.enforceAsciiDigits_ = false;
  148. /**
  149. * Set if the usage of Ascii digits in formatting should be enforced.
  150. * @param {boolean} doEnforce Boolean value about if Ascii digits should be
  151. * enforced.
  152. */
  153. goog.i18n.NumberFormat.setEnforceAsciiDigits = function(doEnforce) {
  154. goog.i18n.NumberFormat.enforceAsciiDigits_ = doEnforce;
  155. };
  156. /**
  157. * Return if Ascii digits is enforced.
  158. * @return {boolean} If Ascii digits is enforced.
  159. */
  160. goog.i18n.NumberFormat.isEnforceAsciiDigits = function() {
  161. return goog.i18n.NumberFormat.enforceAsciiDigits_;
  162. };
  163. /**
  164. * Returns the current NumberFormatSymbols.
  165. * @return {!Object}
  166. * @private
  167. */
  168. goog.i18n.NumberFormat.getNumberFormatSymbols_ = function() {
  169. return goog.i18n.NumberFormat.enforceAsciiDigits_ ?
  170. goog.i18n.NumberFormatSymbols_u_nu_latn :
  171. goog.i18n.NumberFormatSymbols;
  172. };
  173. /**
  174. * Returns the currency code.
  175. * @return {string}
  176. * @private
  177. */
  178. goog.i18n.NumberFormat.prototype.getCurrencyCode_ = function() {
  179. return this.intlCurrencyCode_ ||
  180. goog.i18n.NumberFormat.getNumberFormatSymbols_().DEF_CURRENCY_CODE;
  181. };
  182. /**
  183. * Sets minimum number of fraction digits.
  184. * @param {number} min the minimum.
  185. * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
  186. */
  187. goog.i18n.NumberFormat.prototype.setMinimumFractionDigits = function(min) {
  188. if (this.significantDigits_ > 0 && min > 0) {
  189. throw Error(
  190. 'Can\'t combine significant digits and minimum fraction digits');
  191. }
  192. this.minimumFractionDigits_ = min;
  193. return this;
  194. };
  195. /**
  196. * Sets maximum number of fraction digits.
  197. * @param {number} max the maximum.
  198. * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
  199. */
  200. goog.i18n.NumberFormat.prototype.setMaximumFractionDigits = function(max) {
  201. if (max > 308) {
  202. // Math.pow(10, 309) becomes Infinity which breaks the logic in this class.
  203. throw Error('Unsupported maximum fraction digits: ' + max);
  204. }
  205. this.maximumFractionDigits_ = max;
  206. return this;
  207. };
  208. /**
  209. * Sets number of significant digits to show. Only fractions will be rounded.
  210. * Regardless of the number of significant digits set, the number of fractional
  211. * digits shown will always be capped by the maximum number of fractional digits
  212. * set on {@link #setMaximumFractionDigits}.
  213. * @param {number} number The number of significant digits to include.
  214. * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
  215. */
  216. goog.i18n.NumberFormat.prototype.setSignificantDigits = function(number) {
  217. if (this.minimumFractionDigits_ > 0 && number >= 0) {
  218. throw Error(
  219. 'Can\'t combine significant digits and minimum fraction digits');
  220. }
  221. this.significantDigits_ = number;
  222. return this;
  223. };
  224. /**
  225. * Gets number of significant digits to show. Only fractions will be rounded.
  226. * @return {number} The number of significant digits to include.
  227. */
  228. goog.i18n.NumberFormat.prototype.getSignificantDigits = function() {
  229. return this.significantDigits_;
  230. };
  231. /**
  232. * Sets whether trailing fraction zeros should be shown when significantDigits_
  233. * is positive. If this is true and significantDigits_ is 2, 1 will be formatted
  234. * as '1.0'.
  235. * @param {boolean} showTrailingZeros Whether trailing zeros should be shown.
  236. * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
  237. */
  238. goog.i18n.NumberFormat.prototype.setShowTrailingZeros = function(
  239. showTrailingZeros) {
  240. this.showTrailingZeros_ = showTrailingZeros;
  241. return this;
  242. };
  243. /**
  244. * Sets a number to base the formatting on when compact style formatting is
  245. * used. If this is null, the formatting should be based only on the number to
  246. * be formatting.
  247. *
  248. * This base formatting number can be used to format the target number as
  249. * another number would be formatted. For example, 100,000 is normally formatted
  250. * as "100K" in the COMPACT_SHORT format. To instead format it as '0.1M', the
  251. * base number could be set to 1,000,000 in order to force all numbers to be
  252. * formatted in millions. Similarly, 1,000,000,000 would normally be formatted
  253. * as '1B' and setting the base formatting number to 1,000,000, would cause it
  254. * to be formatted instead as '1,000M'.
  255. *
  256. * @param {?number} baseFormattingNumber The number to base formatting on, or
  257. * null if formatting should not be based on another number.
  258. * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
  259. */
  260. goog.i18n.NumberFormat.prototype.setBaseFormatting = function(
  261. baseFormattingNumber) {
  262. goog.asserts.assert(
  263. goog.isNull(baseFormattingNumber) || isFinite(baseFormattingNumber));
  264. this.baseFormattingNumber_ = baseFormattingNumber;
  265. return this;
  266. };
  267. /**
  268. * Gets the number on which compact formatting is currently based, or null if
  269. * no such number is set. See setBaseFormatting() for more information.
  270. * @return {?number}
  271. */
  272. goog.i18n.NumberFormat.prototype.getBaseFormatting = function() {
  273. return this.baseFormattingNumber_;
  274. };
  275. /**
  276. * Apply provided pattern, result are stored in member variables.
  277. *
  278. * @param {string} pattern String pattern being applied.
  279. * @private
  280. */
  281. goog.i18n.NumberFormat.prototype.applyPattern_ = function(pattern) {
  282. this.pattern_ = pattern.replace(/ /g, '\u00a0');
  283. var pos = [0];
  284. this.positivePrefix_ = this.parseAffix_(pattern, pos);
  285. var trunkStart = pos[0];
  286. this.parseTrunk_(pattern, pos);
  287. var trunkLen = pos[0] - trunkStart;
  288. this.positiveSuffix_ = this.parseAffix_(pattern, pos);
  289. if (pos[0] < pattern.length &&
  290. pattern.charAt(pos[0]) == goog.i18n.NumberFormat.PATTERN_SEPARATOR_) {
  291. pos[0]++;
  292. if (this.multiplier_ != 1) this.negativePercentSignExpected_ = true;
  293. this.negativePrefix_ = this.parseAffix_(pattern, pos);
  294. // we assume this part is identical to positive part.
  295. // user must make sure the pattern is correctly constructed.
  296. pos[0] += trunkLen;
  297. this.negativeSuffix_ = this.parseAffix_(pattern, pos);
  298. } else {
  299. // if no negative affix specified, they share the same positive affix
  300. this.negativePrefix_ += this.positivePrefix_;
  301. this.negativeSuffix_ += this.positiveSuffix_;
  302. }
  303. };
  304. /**
  305. * Apply a predefined pattern to NumberFormat object.
  306. * @param {number} patternType The number that indicates a predefined number
  307. * format pattern.
  308. * @private
  309. */
  310. goog.i18n.NumberFormat.prototype.applyStandardPattern_ = function(patternType) {
  311. switch (patternType) {
  312. case goog.i18n.NumberFormat.Format.DECIMAL:
  313. this.applyPattern_(
  314. goog.i18n.NumberFormat.getNumberFormatSymbols_().DECIMAL_PATTERN);
  315. break;
  316. case goog.i18n.NumberFormat.Format.SCIENTIFIC:
  317. this.applyPattern_(
  318. goog.i18n.NumberFormat.getNumberFormatSymbols_().SCIENTIFIC_PATTERN);
  319. break;
  320. case goog.i18n.NumberFormat.Format.PERCENT:
  321. this.applyPattern_(
  322. goog.i18n.NumberFormat.getNumberFormatSymbols_().PERCENT_PATTERN);
  323. break;
  324. case goog.i18n.NumberFormat.Format.CURRENCY:
  325. this.applyPattern_(goog.i18n.currency.adjustPrecision(
  326. goog.i18n.NumberFormat.getNumberFormatSymbols_().CURRENCY_PATTERN,
  327. this.getCurrencyCode_()));
  328. break;
  329. case goog.i18n.NumberFormat.Format.COMPACT_SHORT:
  330. this.applyCompactStyle_(goog.i18n.NumberFormat.CompactStyle.SHORT);
  331. break;
  332. case goog.i18n.NumberFormat.Format.COMPACT_LONG:
  333. this.applyCompactStyle_(goog.i18n.NumberFormat.CompactStyle.LONG);
  334. break;
  335. default:
  336. throw Error('Unsupported pattern type.');
  337. }
  338. };
  339. /**
  340. * Apply a predefined pattern for shorthand formats.
  341. * @param {goog.i18n.NumberFormat.CompactStyle} style the compact style to
  342. * set defaults for.
  343. * @private
  344. */
  345. goog.i18n.NumberFormat.prototype.applyCompactStyle_ = function(style) {
  346. this.compactStyle_ = style;
  347. this.applyPattern_(
  348. goog.i18n.NumberFormat.getNumberFormatSymbols_().DECIMAL_PATTERN);
  349. this.setMinimumFractionDigits(0);
  350. this.setMaximumFractionDigits(2);
  351. this.setSignificantDigits(2);
  352. };
  353. /**
  354. * Parses text string to produce a Number.
  355. *
  356. * This method attempts to parse text starting from position "opt_pos" if it
  357. * is given. Otherwise the parse will start from the beginning of the text.
  358. * When opt_pos presents, opt_pos will be updated to the character next to where
  359. * parsing stops after the call. If an error occurs, opt_pos won't be updated.
  360. *
  361. * @param {string} text The string to be parsed.
  362. * @param {Array<number>=} opt_pos Position to pass in and get back.
  363. * @return {number} Parsed number. This throws an error if the text cannot be
  364. * parsed.
  365. */
  366. goog.i18n.NumberFormat.prototype.parse = function(text, opt_pos) {
  367. var pos = opt_pos || [0];
  368. if (this.compactStyle_ != goog.i18n.NumberFormat.CompactStyle.NONE) {
  369. throw Error('Parsing of compact numbers is unimplemented');
  370. }
  371. var ret = NaN;
  372. // we don't want to handle 2 kind of space in parsing, normalize it to nbsp
  373. text = text.replace(/ /g, '\u00a0');
  374. var gotPositive = text.indexOf(this.positivePrefix_, pos[0]) == pos[0];
  375. var gotNegative = text.indexOf(this.negativePrefix_, pos[0]) == pos[0];
  376. // check for the longest match
  377. if (gotPositive && gotNegative) {
  378. if (this.positivePrefix_.length > this.negativePrefix_.length) {
  379. gotNegative = false;
  380. } else if (this.positivePrefix_.length < this.negativePrefix_.length) {
  381. gotPositive = false;
  382. }
  383. }
  384. if (gotPositive) {
  385. pos[0] += this.positivePrefix_.length;
  386. } else if (gotNegative) {
  387. pos[0] += this.negativePrefix_.length;
  388. }
  389. // process digits or Inf, find decimal position
  390. if (text.indexOf(
  391. goog.i18n.NumberFormat.getNumberFormatSymbols_().INFINITY, pos[0]) ==
  392. pos[0]) {
  393. pos[0] += goog.i18n.NumberFormat.getNumberFormatSymbols_().INFINITY.length;
  394. ret = Infinity;
  395. } else {
  396. ret = this.parseNumber_(text, pos);
  397. }
  398. // check for suffix
  399. if (gotPositive) {
  400. if (!(text.indexOf(this.positiveSuffix_, pos[0]) == pos[0])) {
  401. return NaN;
  402. }
  403. pos[0] += this.positiveSuffix_.length;
  404. } else if (gotNegative) {
  405. if (!(text.indexOf(this.negativeSuffix_, pos[0]) == pos[0])) {
  406. return NaN;
  407. }
  408. pos[0] += this.negativeSuffix_.length;
  409. }
  410. return gotNegative ? -ret : ret;
  411. };
  412. /**
  413. * This function will parse a "localized" text into a Number. It needs to
  414. * handle locale specific decimal, grouping, exponent and digits.
  415. *
  416. * @param {string} text The text that need to be parsed.
  417. * @param {Array<number>} pos In/out parsing position. In case of failure,
  418. * pos value won't be changed.
  419. * @return {number} Number value, or NaN if nothing can be parsed.
  420. * @private
  421. */
  422. goog.i18n.NumberFormat.prototype.parseNumber_ = function(text, pos) {
  423. var sawDecimal = false;
  424. var sawExponent = false;
  425. var sawDigit = false;
  426. var exponentPos = -1;
  427. var scale = 1;
  428. var decimal = goog.i18n.NumberFormat.getNumberFormatSymbols_().DECIMAL_SEP;
  429. var grouping = goog.i18n.NumberFormat.getNumberFormatSymbols_().GROUP_SEP;
  430. var exponentChar =
  431. goog.i18n.NumberFormat.getNumberFormatSymbols_().EXP_SYMBOL;
  432. if (this.compactStyle_ != goog.i18n.NumberFormat.CompactStyle.NONE) {
  433. throw Error('Parsing of compact style numbers is not implemented');
  434. }
  435. var normalizedText = '';
  436. for (; pos[0] < text.length; pos[0]++) {
  437. var ch = text.charAt(pos[0]);
  438. var digit = this.getDigit_(ch);
  439. if (digit >= 0 && digit <= 9) {
  440. normalizedText += digit;
  441. sawDigit = true;
  442. } else if (ch == decimal.charAt(0)) {
  443. if (sawDecimal || sawExponent) {
  444. break;
  445. }
  446. normalizedText += '.';
  447. sawDecimal = true;
  448. } else if (
  449. ch == grouping.charAt(0) &&
  450. ('\u00a0' != grouping.charAt(0) ||
  451. pos[0] + 1 < text.length &&
  452. this.getDigit_(text.charAt(pos[0] + 1)) >= 0)) {
  453. // Got a grouping character here. When grouping character is nbsp, need
  454. // to make sure the character following it is a digit.
  455. if (sawDecimal || sawExponent) {
  456. break;
  457. }
  458. continue;
  459. } else if (ch == exponentChar.charAt(0)) {
  460. if (sawExponent) {
  461. break;
  462. }
  463. normalizedText += 'E';
  464. sawExponent = true;
  465. exponentPos = pos[0];
  466. } else if (ch == '+' || ch == '-') {
  467. // Stop parsing if a '+' or '-' sign is found after digits have been found
  468. // but it's not located right after an exponent sign.
  469. if (sawDigit && exponentPos != pos[0] - 1) {
  470. break;
  471. }
  472. normalizedText += ch;
  473. } else if (
  474. this.multiplier_ == 1 &&
  475. ch ==
  476. goog.i18n.NumberFormat.getNumberFormatSymbols_().PERCENT.charAt(
  477. 0)) {
  478. // Parse the percent character as part of the number only when it's
  479. // not already included in the pattern.
  480. if (scale != 1) {
  481. break;
  482. }
  483. scale = 100;
  484. if (sawDigit) {
  485. pos[0]++; // eat this character if parse end here
  486. break;
  487. }
  488. } else if (
  489. this.multiplier_ == 1 &&
  490. ch ==
  491. goog.i18n.NumberFormat.getNumberFormatSymbols_().PERMILL.charAt(
  492. 0)) {
  493. // Parse the permill character as part of the number only when it's
  494. // not already included in the pattern.
  495. if (scale != 1) {
  496. break;
  497. }
  498. scale = 1000;
  499. if (sawDigit) {
  500. pos[0]++; // eat this character if parse end here
  501. break;
  502. }
  503. } else {
  504. break;
  505. }
  506. }
  507. // Scale the number when the percent/permill character was included in
  508. // the pattern.
  509. if (this.multiplier_ != 1) {
  510. scale = this.multiplier_;
  511. }
  512. return parseFloat(normalizedText) / scale;
  513. };
  514. /**
  515. * Formats a Number to produce a string.
  516. *
  517. * @param {number} number The Number to be formatted.
  518. * @return {string} The formatted number string.
  519. */
  520. goog.i18n.NumberFormat.prototype.format = function(number) {
  521. if (isNaN(number)) {
  522. return goog.i18n.NumberFormat.getNumberFormatSymbols_().NAN;
  523. }
  524. var parts = [];
  525. var baseFormattingNumber = goog.isNull(this.baseFormattingNumber_) ?
  526. number :
  527. this.baseFormattingNumber_;
  528. var unit = this.getUnitAfterRounding_(baseFormattingNumber, number);
  529. number /= Math.pow(10, unit.divisorBase);
  530. parts.push(unit.prefix);
  531. // in icu code, it is commented that certain computation need to keep the
  532. // negative sign for 0.
  533. var isNegative = number < 0.0 || number == 0.0 && 1 / number < 0.0;
  534. parts.push(isNegative ? this.negativePrefix_ : this.positivePrefix_);
  535. if (!isFinite(number)) {
  536. parts.push(goog.i18n.NumberFormat.getNumberFormatSymbols_().INFINITY);
  537. } else {
  538. // convert number to non-negative value
  539. number *= isNegative ? -1 : 1;
  540. number *= this.multiplier_;
  541. this.useExponentialNotation_ ?
  542. this.subformatExponential_(number, parts) :
  543. this.subformatFixed_(number, this.minimumIntegerDigits_, parts);
  544. }
  545. parts.push(isNegative ? this.negativeSuffix_ : this.positiveSuffix_);
  546. parts.push(unit.suffix);
  547. return parts.join('');
  548. };
  549. /**
  550. * Round a number into an integer and fractional part
  551. * based on the rounding rules for this NumberFormat.
  552. * @param {number} number The number to round.
  553. * @return {{intValue: number, fracValue: number}} The integer and fractional
  554. * part after rounding.
  555. * @private
  556. */
  557. goog.i18n.NumberFormat.prototype.roundNumber_ = function(number) {
  558. var power = Math.pow(10, this.maximumFractionDigits_);
  559. var shiftedNumber = this.significantDigits_ <= 0 ?
  560. Math.round(number * power) :
  561. Math.round(
  562. this.roundToSignificantDigits_(
  563. number * power, this.significantDigits_,
  564. this.maximumFractionDigits_));
  565. var intValue, fracValue;
  566. if (isFinite(shiftedNumber)) {
  567. intValue = Math.floor(shiftedNumber / power);
  568. fracValue = Math.floor(shiftedNumber - intValue * power);
  569. } else {
  570. intValue = number;
  571. fracValue = 0;
  572. }
  573. return {intValue: intValue, fracValue: fracValue};
  574. };
  575. /**
  576. * Formats a number with the appropriate groupings when there are repeating
  577. * digits present. Repeating digits exists when the length of the digits left
  578. * of the decimal place exceeds the number of non-repeating digits.
  579. *
  580. * Formats a number by iterating through the integer number (intPart) from the
  581. * most left of the decimal place by inserting the appropriate number grouping
  582. * separator for the repeating digits until all of the repeating digits is
  583. * iterated. Then iterate through the non-repeating digits by inserting the
  584. * appropriate number grouping separator until all the non-repeating digits
  585. * is iterated through.
  586. *
  587. * In the number grouping concept, anything left of the decimal
  588. * place is followed by non-repeating digits and then repeating digits. If the
  589. * pattern is #,##,###, then we first (from the left of the decimal place) have
  590. * a non-repeating digit of size 3 followed by repeating digits of size 2
  591. * separated by a thousand separator. If the length of the digits are six or
  592. * more, there may be repeating digits required. For example, the value of
  593. * 12345678 would format as 1,23,45,678 where the repeating digit is length 2.
  594. *
  595. * @param {!Array<string>} parts An array to build the 'parts' of the formatted
  596. * number including the values and separators.
  597. * @param {number} zeroCode The value of the zero digit whether or not
  598. * goog.i18n.NumberFormat.enforceAsciiDigits_ is enforced.
  599. * @param {string} intPart The integer representation of the number to be
  600. * formatted and referenced.
  601. * @param {!Array<number>} groupingArray The array of numbers to determine the
  602. * grouping of repeated and non-repeated digits.
  603. * @param {number} repeatedDigitLen The length of the repeated digits left of
  604. * the non-repeating digits left of the decimal.
  605. * @return {!Array<string>} Returns the resulting parts variable containing
  606. * how numbers are to be grouped and appear.
  607. * @private
  608. */
  609. goog.i18n.NumberFormat.formatNumberGroupingRepeatingDigitsParts_ = function(
  610. parts, zeroCode, intPart, groupingArray, repeatedDigitLen) {
  611. // Keep track of how much has been completed on the non repeated groups
  612. var nonRepeatedGroupCompleteCount = 0;
  613. var currentGroupSizeIndex = 0;
  614. var currentGroupSize = 0;
  615. var grouping = goog.i18n.NumberFormat.getNumberFormatSymbols_().GROUP_SEP;
  616. var digitLen = intPart.length;
  617. // There are repeating digits and non-repeating digits
  618. for (var i = 0; i < digitLen; i++) {
  619. parts.push(String.fromCharCode(zeroCode + Number(intPart.charAt(i)) * 1));
  620. if (digitLen - i > 1) {
  621. currentGroupSize = groupingArray[currentGroupSizeIndex];
  622. if (i < repeatedDigitLen) {
  623. // Process the left side (the repeated number groups)
  624. var repeatedDigitIndex = repeatedDigitLen - i;
  625. // Edge case if there's a number grouping asking for "1" group at
  626. // a time; otherwise, if the remainder is 1, there's the separator
  627. if (currentGroupSize === 1 ||
  628. (currentGroupSize > 0 &&
  629. (repeatedDigitIndex % currentGroupSize) === 1)) {
  630. parts.push(grouping);
  631. }
  632. } else if (currentGroupSizeIndex < groupingArray.length) {
  633. // Process the right side (the non-repeated fixed number groups)
  634. if (i === repeatedDigitLen) {
  635. // Increase the group index because a separator
  636. // has previously added in the earlier logic
  637. currentGroupSizeIndex += 1;
  638. } else if (
  639. currentGroupSize ===
  640. i - repeatedDigitLen - nonRepeatedGroupCompleteCount + 1) {
  641. // Otherwise, just iterate to the right side and
  642. // add a separator once the length matches to the expected
  643. parts.push(grouping);
  644. // Keep track of what has been completed on the right
  645. nonRepeatedGroupCompleteCount += currentGroupSize;
  646. currentGroupSizeIndex += 1; // Get to the next number grouping
  647. }
  648. }
  649. }
  650. }
  651. return parts;
  652. };
  653. /**
  654. * Formats a number with the appropriate groupings when there are no repeating
  655. * digits present. Non-repeating digits exists when the length of the digits
  656. * left of the decimal place is equal or lesser than the length of
  657. * non-repeating digits.
  658. *
  659. * Formats a number by iterating through the integer number (intPart) from the
  660. * right most non-repeating number group of the decimal place. For each group,
  661. * inserting the appropriate number grouping separator for the non-repeating
  662. * digits until the number is completely iterated.
  663. *
  664. * In the number grouping concept, anything left of the decimal
  665. * place is followed by non-repeating digits and then repeating digits. If the
  666. * pattern is #,##,###, then we first (from the left of the decimal place) have
  667. * a non-repeating digit of size 3 followed by repeating digits of size 2
  668. * separated by a thousand separator. If the length of the digits are five or
  669. * less, there won't be any repeating digits required. For example, the value
  670. * of 12345 would be formatted as 12,345 where the non-repeating digit is of
  671. * length 3.
  672. *
  673. * @param {!Array<string>} parts An array to build the 'parts' of the formatted
  674. * number including the values and separators.
  675. * @param {number} zeroCode The value of the zero digit whether or not
  676. * goog.i18n.NumberFormat.enforceAsciiDigits_ is enforced.
  677. * @param {string} intPart The integer representation of the number to be
  678. * formatted and referenced.
  679. * @param {!Array<number>} groupingArray The array of numbers to determine the
  680. * grouping of repeated and non-repeated digits.
  681. * @return {!Array<string>} Returns the resulting parts variable containing
  682. * how numbers are to be grouped and appear.
  683. * @private
  684. */
  685. goog.i18n.NumberFormat.formatNumberGroupingNonRepeatingDigitsParts_ = function(
  686. parts, zeroCode, intPart, groupingArray) {
  687. // Keep track of how much has been completed on the non repeated groups
  688. var grouping = goog.i18n.NumberFormat.getNumberFormatSymbols_().GROUP_SEP;
  689. var currentGroupSizeIndex;
  690. var currentGroupSize = 0;
  691. var digitLenLeft = intPart.length;
  692. var rightToLeftParts = [];
  693. // Start from the right most non-repeating group and work inwards
  694. for (currentGroupSizeIndex = groupingArray.length - 1;
  695. currentGroupSizeIndex >= 0 && digitLenLeft > 0;
  696. currentGroupSizeIndex--) {
  697. currentGroupSize = groupingArray[currentGroupSizeIndex];
  698. // Iterate from the right most digit
  699. for (var rightDigitIndex = 0; rightDigitIndex < currentGroupSize &&
  700. ((digitLenLeft - rightDigitIndex - 1) >= 0);
  701. rightDigitIndex++) {
  702. rightToLeftParts.push(
  703. String.fromCharCode(
  704. zeroCode +
  705. Number(intPart.charAt(digitLenLeft - rightDigitIndex - 1)) * 1));
  706. }
  707. // Update the number of digits left
  708. digitLenLeft -= currentGroupSize;
  709. if (digitLenLeft > 0) {
  710. rightToLeftParts.push(grouping);
  711. }
  712. }
  713. // Reverse and push onto the remaining parts
  714. parts.push.apply(parts, rightToLeftParts.reverse());
  715. return parts;
  716. };
  717. /**
  718. * Formats a Number in fraction format.
  719. *
  720. * @param {number} number
  721. * @param {number} minIntDigits Minimum integer digits.
  722. * @param {Array<string>} parts
  723. * This array holds the pieces of formatted string.
  724. * This function will add its formatted pieces to the array.
  725. * @private
  726. */
  727. goog.i18n.NumberFormat.prototype.subformatFixed_ = function(
  728. number, minIntDigits, parts) {
  729. if (this.minimumFractionDigits_ > this.maximumFractionDigits_) {
  730. throw Error('Min value must be less than max value');
  731. }
  732. if (!parts) {
  733. parts = [];
  734. }
  735. var rounded = this.roundNumber_(number);
  736. var intValue = rounded.intValue;
  737. var fracValue = rounded.fracValue;
  738. var numIntDigits = (intValue == 0) ? 0 : this.intLog10_(intValue) + 1;
  739. var fractionPresent = this.minimumFractionDigits_ > 0 || fracValue > 0 ||
  740. (this.showTrailingZeros_ && numIntDigits < this.significantDigits_);
  741. var minimumFractionDigits = this.minimumFractionDigits_;
  742. if (fractionPresent) {
  743. if (this.showTrailingZeros_ && this.significantDigits_ > 0) {
  744. minimumFractionDigits = this.significantDigits_ - numIntDigits;
  745. } else {
  746. minimumFractionDigits = this.minimumFractionDigits_;
  747. }
  748. }
  749. var intPart = '';
  750. var translatableInt = intValue;
  751. while (translatableInt > 1E20) {
  752. // here it goes beyond double precision, add '0' make it look better
  753. intPart = '0' + intPart;
  754. translatableInt = Math.round(translatableInt / 10);
  755. }
  756. intPart = translatableInt + intPart;
  757. var decimal = goog.i18n.NumberFormat.getNumberFormatSymbols_().DECIMAL_SEP;
  758. var zeroCode =
  759. goog.i18n.NumberFormat.getNumberFormatSymbols_().ZERO_DIGIT.charCodeAt(0);
  760. var digitLen = intPart.length;
  761. var nonRepeatedGroupCount = 0;
  762. if (intValue > 0 || minIntDigits > 0) {
  763. for (var i = digitLen; i < minIntDigits; i++) {
  764. parts.push(String.fromCharCode(zeroCode));
  765. }
  766. // If there's more than 1 number grouping,
  767. // figure out the length of the non-repeated groupings (on the right)
  768. if (this.groupingArray_.length >= 2) {
  769. for (var j = 1; j < this.groupingArray_.length; j++) {
  770. nonRepeatedGroupCount += this.groupingArray_[j];
  771. }
  772. }
  773. // Anything left of the fixed number grouping is repeated,
  774. // figure out the length of repeated groupings (on the left)
  775. var repeatedDigitLen = digitLen - nonRepeatedGroupCount;
  776. if (repeatedDigitLen > 0) {
  777. // There are repeating digits and non-repeating digits
  778. parts = goog.i18n.NumberFormat.formatNumberGroupingRepeatingDigitsParts_(
  779. parts, zeroCode, intPart, this.groupingArray_, repeatedDigitLen);
  780. } else {
  781. // There are no repeating digits and only non-repeating digits
  782. parts =
  783. goog.i18n.NumberFormat.formatNumberGroupingNonRepeatingDigitsParts_(
  784. parts, zeroCode, intPart, this.groupingArray_);
  785. }
  786. } else if (!fractionPresent) {
  787. // If there is no fraction present, and we haven't printed any
  788. // integer digits, then print a zero.
  789. parts.push(String.fromCharCode(zeroCode));
  790. }
  791. // Output the decimal separator if we always do so.
  792. if (this.decimalSeparatorAlwaysShown_ || fractionPresent) {
  793. parts.push(decimal);
  794. }
  795. var fracPart = String(fracValue);
  796. // Handle case where fracPart is in scientific notation.
  797. var fracPartSplit = fracPart.split('e+');
  798. if (fracPartSplit.length == 2) {
  799. // Only keep significant digits.
  800. var floatFrac = parseFloat(fracPartSplit[0]);
  801. fracPart = String(
  802. this.roundToSignificantDigits_(floatFrac, this.significantDigits_, 1));
  803. fracPart = fracPart.replace('.', '');
  804. // Append zeroes based on the exponent.
  805. var exp = parseInt(fracPartSplit[1], 10);
  806. fracPart += goog.string.repeat('0', exp - fracPart.length + 1);
  807. }
  808. // Add Math.pow(10, this.maximumFractionDigits) to fracPart. Uses string ops
  809. // to avoid complexity with scientific notation and overflows.
  810. if (this.maximumFractionDigits_ + 1 > fracPart.length) {
  811. var zeroesToAdd = this.maximumFractionDigits_ - fracPart.length;
  812. fracPart = '1' + goog.string.repeat('0', zeroesToAdd) + fracPart;
  813. }
  814. var fracLen = fracPart.length;
  815. while (fracPart.charAt(fracLen - 1) == '0' &&
  816. fracLen > minimumFractionDigits + 1) {
  817. fracLen--;
  818. }
  819. for (var i = 1; i < fracLen; i++) {
  820. parts.push(String.fromCharCode(zeroCode + Number(fracPart.charAt(i)) * 1));
  821. }
  822. };
  823. /**
  824. * Formats exponent part of a Number.
  825. *
  826. * @param {number} exponent Exponential value.
  827. * @param {Array<string>} parts The array that holds the pieces of formatted
  828. * string. This function will append more formatted pieces to the array.
  829. * @private
  830. */
  831. goog.i18n.NumberFormat.prototype.addExponentPart_ = function(exponent, parts) {
  832. parts.push(goog.i18n.NumberFormat.getNumberFormatSymbols_().EXP_SYMBOL);
  833. if (exponent < 0) {
  834. exponent = -exponent;
  835. parts.push(goog.i18n.NumberFormat.getNumberFormatSymbols_().MINUS_SIGN);
  836. } else if (this.useSignForPositiveExponent_) {
  837. parts.push(goog.i18n.NumberFormat.getNumberFormatSymbols_().PLUS_SIGN);
  838. }
  839. var exponentDigits = '' + exponent;
  840. var zeroChar = goog.i18n.NumberFormat.getNumberFormatSymbols_().ZERO_DIGIT;
  841. for (var i = exponentDigits.length; i < this.minExponentDigits_; i++) {
  842. parts.push(zeroChar);
  843. }
  844. parts.push(exponentDigits);
  845. };
  846. /**
  847. * Returns the mantissa for the given value and its exponent.
  848. *
  849. * @param {number} value
  850. * @param {number} exponent
  851. * @return {number}
  852. * @private
  853. */
  854. goog.i18n.NumberFormat.prototype.getMantissa_ = function(value, exponent) {
  855. var divisor = Math.pow(10, exponent);
  856. if (isFinite(divisor) && divisor !== 0) {
  857. return value / divisor;
  858. } else {
  859. // If the exponent is too big pow returns 0. In such a case we calculate
  860. // half of the divisor and apply it twice.
  861. divisor = Math.pow(10, Math.floor(exponent / 2));
  862. var result = value / divisor / divisor;
  863. if (exponent % 2 == 1) { // Correcting for odd exponents.
  864. if (exponent > 0) {
  865. result /= 10;
  866. } else {
  867. result *= 10;
  868. }
  869. }
  870. return result;
  871. }
  872. };
  873. /**
  874. * Formats Number in exponential format.
  875. *
  876. * @param {number} number Value need to be formatted.
  877. * @param {Array<string>} parts The array that holds the pieces of formatted
  878. * string. This function will append more formatted pieces to the array.
  879. * @private
  880. */
  881. goog.i18n.NumberFormat.prototype.subformatExponential_ = function(
  882. number, parts) {
  883. if (number == 0.0) {
  884. this.subformatFixed_(number, this.minimumIntegerDigits_, parts);
  885. this.addExponentPart_(0, parts);
  886. return;
  887. }
  888. var exponent = goog.math.safeFloor(Math.log(number) / Math.log(10));
  889. number = this.getMantissa_(number, exponent);
  890. var minIntDigits = this.minimumIntegerDigits_;
  891. if (this.maximumIntegerDigits_ > 1 &&
  892. this.maximumIntegerDigits_ > this.minimumIntegerDigits_) {
  893. // A repeating range is defined; adjust to it as follows.
  894. // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
  895. // -3,-4,-5=>-6, etc. This takes into account that the
  896. // exponent we have here is off by one from what we expect;
  897. // it is for the format 0.MMMMMx10^n.
  898. while ((exponent % this.maximumIntegerDigits_) != 0) {
  899. number *= 10;
  900. exponent--;
  901. }
  902. minIntDigits = 1;
  903. } else {
  904. // No repeating range is defined; use minimum integer digits.
  905. if (this.minimumIntegerDigits_ < 1) {
  906. exponent++;
  907. number /= 10;
  908. } else {
  909. exponent -= this.minimumIntegerDigits_ - 1;
  910. number *= Math.pow(10, this.minimumIntegerDigits_ - 1);
  911. }
  912. }
  913. this.subformatFixed_(number, minIntDigits, parts);
  914. this.addExponentPart_(exponent, parts);
  915. };
  916. /**
  917. * Returns the digit value of current character. The character could be either
  918. * '0' to '9', or a locale specific digit.
  919. *
  920. * @param {string} ch Character that represents a digit.
  921. * @return {number} The digit value, or -1 on error.
  922. * @private
  923. */
  924. goog.i18n.NumberFormat.prototype.getDigit_ = function(ch) {
  925. var code = ch.charCodeAt(0);
  926. // between '0' to '9'
  927. if (48 <= code && code < 58) {
  928. return code - 48;
  929. } else {
  930. var zeroCode =
  931. goog.i18n.NumberFormat.getNumberFormatSymbols_().ZERO_DIGIT.charCodeAt(
  932. 0);
  933. return zeroCode <= code && code < zeroCode + 10 ? code - zeroCode : -1;
  934. }
  935. };
  936. // ----------------------------------------------------------------------
  937. // CONSTANTS
  938. // ----------------------------------------------------------------------
  939. // Constants for characters used in programmatic (unlocalized) patterns.
  940. /**
  941. * A zero digit character.
  942. * @type {string}
  943. * @private
  944. */
  945. goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_ = '0';
  946. /**
  947. * A grouping separator character.
  948. * @type {string}
  949. * @private
  950. */
  951. goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_ = ',';
  952. /**
  953. * A decimal separator character.
  954. * @type {string}
  955. * @private
  956. */
  957. goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_ = '.';
  958. /**
  959. * A per mille character.
  960. * @type {string}
  961. * @private
  962. */
  963. goog.i18n.NumberFormat.PATTERN_PER_MILLE_ = '\u2030';
  964. /**
  965. * A percent character.
  966. * @type {string}
  967. * @private
  968. */
  969. goog.i18n.NumberFormat.PATTERN_PERCENT_ = '%';
  970. /**
  971. * A digit character.
  972. * @type {string}
  973. * @private
  974. */
  975. goog.i18n.NumberFormat.PATTERN_DIGIT_ = '#';
  976. /**
  977. * A separator character.
  978. * @type {string}
  979. * @private
  980. */
  981. goog.i18n.NumberFormat.PATTERN_SEPARATOR_ = ';';
  982. /**
  983. * An exponent character.
  984. * @type {string}
  985. * @private
  986. */
  987. goog.i18n.NumberFormat.PATTERN_EXPONENT_ = 'E';
  988. /**
  989. * A plus character.
  990. * @type {string}
  991. * @private
  992. */
  993. goog.i18n.NumberFormat.PATTERN_PLUS_ = '+';
  994. /**
  995. * A generic currency sign character.
  996. * @type {string}
  997. * @private
  998. */
  999. goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_ = '\u00A4';
  1000. /**
  1001. * A quote character.
  1002. * @type {string}
  1003. * @private
  1004. */
  1005. goog.i18n.NumberFormat.QUOTE_ = '\'';
  1006. /**
  1007. * Parses affix part of pattern.
  1008. *
  1009. * @param {string} pattern Pattern string that need to be parsed.
  1010. * @param {Array<number>} pos One element position array to set and receive
  1011. * parsing position.
  1012. *
  1013. * @return {string} Affix received from parsing.
  1014. * @private
  1015. */
  1016. goog.i18n.NumberFormat.prototype.parseAffix_ = function(pattern, pos) {
  1017. var affix = '';
  1018. var inQuote = false;
  1019. var len = pattern.length;
  1020. for (; pos[0] < len; pos[0]++) {
  1021. var ch = pattern.charAt(pos[0]);
  1022. if (ch == goog.i18n.NumberFormat.QUOTE_) {
  1023. if (pos[0] + 1 < len &&
  1024. pattern.charAt(pos[0] + 1) == goog.i18n.NumberFormat.QUOTE_) {
  1025. pos[0]++;
  1026. affix += '\''; // 'don''t'
  1027. } else {
  1028. inQuote = !inQuote;
  1029. }
  1030. continue;
  1031. }
  1032. if (inQuote) {
  1033. affix += ch;
  1034. } else {
  1035. switch (ch) {
  1036. case goog.i18n.NumberFormat.PATTERN_DIGIT_:
  1037. case goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_:
  1038. case goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_:
  1039. case goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_:
  1040. case goog.i18n.NumberFormat.PATTERN_SEPARATOR_:
  1041. return affix;
  1042. case goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_:
  1043. if ((pos[0] + 1) < len &&
  1044. pattern.charAt(pos[0] + 1) ==
  1045. goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_) {
  1046. pos[0]++;
  1047. affix += this.getCurrencyCode_();
  1048. } else {
  1049. switch (this.currencyStyle_) {
  1050. case goog.i18n.NumberFormat.CurrencyStyle.LOCAL:
  1051. affix += goog.i18n.currency.getLocalCurrencySign(
  1052. this.getCurrencyCode_());
  1053. break;
  1054. case goog.i18n.NumberFormat.CurrencyStyle.GLOBAL:
  1055. affix += goog.i18n.currency.getGlobalCurrencySign(
  1056. this.getCurrencyCode_());
  1057. break;
  1058. case goog.i18n.NumberFormat.CurrencyStyle.PORTABLE:
  1059. affix += goog.i18n.currency.getPortableCurrencySign(
  1060. this.getCurrencyCode_());
  1061. break;
  1062. default:
  1063. break;
  1064. }
  1065. }
  1066. break;
  1067. case goog.i18n.NumberFormat.PATTERN_PERCENT_:
  1068. if (!this.negativePercentSignExpected_ && this.multiplier_ != 1) {
  1069. throw Error('Too many percent/permill');
  1070. } else if (
  1071. this.negativePercentSignExpected_ && this.multiplier_ != 100) {
  1072. throw Error('Inconsistent use of percent/permill characters');
  1073. }
  1074. this.multiplier_ = 100;
  1075. this.negativePercentSignExpected_ = false;
  1076. affix += goog.i18n.NumberFormat.getNumberFormatSymbols_().PERCENT;
  1077. break;
  1078. case goog.i18n.NumberFormat.PATTERN_PER_MILLE_:
  1079. if (!this.negativePercentSignExpected_ && this.multiplier_ != 1) {
  1080. throw Error('Too many percent/permill');
  1081. } else if (
  1082. this.negativePercentSignExpected_ && this.multiplier_ != 1000) {
  1083. throw Error('Inconsistent use of percent/permill characters');
  1084. }
  1085. this.multiplier_ = 1000;
  1086. this.negativePercentSignExpected_ = false;
  1087. affix += goog.i18n.NumberFormat.getNumberFormatSymbols_().PERMILL;
  1088. break;
  1089. default:
  1090. affix += ch;
  1091. }
  1092. }
  1093. }
  1094. return affix;
  1095. };
  1096. /**
  1097. * Parses the trunk part of a pattern.
  1098. *
  1099. * @param {string} pattern Pattern string that need to be parsed.
  1100. * @param {Array<number>} pos One element position array to set and receive
  1101. * parsing position.
  1102. * @private
  1103. */
  1104. goog.i18n.NumberFormat.prototype.parseTrunk_ = function(pattern, pos) {
  1105. var decimalPos = -1;
  1106. var digitLeftCount = 0;
  1107. var zeroDigitCount = 0;
  1108. var digitRightCount = 0;
  1109. var groupingCount = -1;
  1110. var len = pattern.length;
  1111. for (var loop = true; pos[0] < len && loop; pos[0]++) {
  1112. var ch = pattern.charAt(pos[0]);
  1113. switch (ch) {
  1114. case goog.i18n.NumberFormat.PATTERN_DIGIT_:
  1115. if (zeroDigitCount > 0) {
  1116. digitRightCount++;
  1117. } else {
  1118. digitLeftCount++;
  1119. }
  1120. if (groupingCount >= 0 && decimalPos < 0) {
  1121. groupingCount++;
  1122. }
  1123. break;
  1124. case goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_:
  1125. if (digitRightCount > 0) {
  1126. throw Error('Unexpected "0" in pattern "' + pattern + '"');
  1127. }
  1128. zeroDigitCount++;
  1129. if (groupingCount >= 0 && decimalPos < 0) {
  1130. groupingCount++;
  1131. }
  1132. break;
  1133. case goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_:
  1134. if (groupingCount > 0) {
  1135. this.groupingArray_.push(groupingCount);
  1136. }
  1137. groupingCount = 0;
  1138. break;
  1139. case goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_:
  1140. if (decimalPos >= 0) {
  1141. throw Error(
  1142. 'Multiple decimal separators in pattern "' + pattern + '"');
  1143. }
  1144. decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
  1145. break;
  1146. case goog.i18n.NumberFormat.PATTERN_EXPONENT_:
  1147. if (this.useExponentialNotation_) {
  1148. throw Error(
  1149. 'Multiple exponential symbols in pattern "' + pattern + '"');
  1150. }
  1151. this.useExponentialNotation_ = true;
  1152. this.minExponentDigits_ = 0;
  1153. // exponent pattern can have a optional '+'.
  1154. if ((pos[0] + 1) < len &&
  1155. pattern.charAt(pos[0] + 1) ==
  1156. goog.i18n.NumberFormat.PATTERN_PLUS_) {
  1157. pos[0]++;
  1158. this.useSignForPositiveExponent_ = true;
  1159. }
  1160. // Use lookahead to parse out the exponential part
  1161. // of the pattern, then jump into phase 2.
  1162. while ((pos[0] + 1) < len &&
  1163. pattern.charAt(pos[0] + 1) ==
  1164. goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_) {
  1165. pos[0]++;
  1166. this.minExponentDigits_++;
  1167. }
  1168. if ((digitLeftCount + zeroDigitCount) < 1 ||
  1169. this.minExponentDigits_ < 1) {
  1170. throw Error('Malformed exponential pattern "' + pattern + '"');
  1171. }
  1172. loop = false;
  1173. break;
  1174. default:
  1175. pos[0]--;
  1176. loop = false;
  1177. break;
  1178. }
  1179. }
  1180. if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
  1181. // Handle '###.###' and '###.' and '.###'
  1182. var n = decimalPos;
  1183. if (n == 0) { // Handle '.###'
  1184. n++;
  1185. }
  1186. digitRightCount = digitLeftCount - n;
  1187. digitLeftCount = n - 1;
  1188. zeroDigitCount = 1;
  1189. }
  1190. // Do syntax checking on the digits.
  1191. if (decimalPos < 0 && digitRightCount > 0 ||
  1192. decimalPos >= 0 && (decimalPos < digitLeftCount ||
  1193. decimalPos > digitLeftCount + zeroDigitCount) ||
  1194. groupingCount == 0) {
  1195. throw Error('Malformed pattern "' + pattern + '"');
  1196. }
  1197. var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
  1198. this.maximumFractionDigits_ = decimalPos >= 0 ? totalDigits - decimalPos : 0;
  1199. if (decimalPos >= 0) {
  1200. this.minimumFractionDigits_ = digitLeftCount + zeroDigitCount - decimalPos;
  1201. if (this.minimumFractionDigits_ < 0) {
  1202. this.minimumFractionDigits_ = 0;
  1203. }
  1204. }
  1205. // The effectiveDecimalPos is the position the decimal is at or would be at
  1206. // if there is no decimal. Note that if decimalPos<0, then digitTotalCount ==
  1207. // digitLeftCount + zeroDigitCount.
  1208. var effectiveDecimalPos = decimalPos >= 0 ? decimalPos : totalDigits;
  1209. this.minimumIntegerDigits_ = effectiveDecimalPos - digitLeftCount;
  1210. if (this.useExponentialNotation_) {
  1211. this.maximumIntegerDigits_ = digitLeftCount + this.minimumIntegerDigits_;
  1212. // in exponential display, we need to at least show something.
  1213. if (this.maximumFractionDigits_ == 0 && this.minimumIntegerDigits_ == 0) {
  1214. this.minimumIntegerDigits_ = 1;
  1215. }
  1216. }
  1217. // Add another number grouping at the end
  1218. this.groupingArray_.push(Math.max(0, groupingCount));
  1219. this.decimalSeparatorAlwaysShown_ =
  1220. decimalPos == 0 || decimalPos == totalDigits;
  1221. };
  1222. /**
  1223. * Alias for the compact format 'unit' object.
  1224. * @typedef {{
  1225. * prefix: string,
  1226. * suffix: string,
  1227. * divisorBase: number
  1228. * }}
  1229. */
  1230. goog.i18n.NumberFormat.CompactNumberUnit;
  1231. /**
  1232. * The empty unit, corresponding to a base of 0.
  1233. * @private {!goog.i18n.NumberFormat.CompactNumberUnit}
  1234. */
  1235. goog.i18n.NumberFormat.NULL_UNIT_ = {
  1236. prefix: '',
  1237. suffix: '',
  1238. divisorBase: 0
  1239. };
  1240. /**
  1241. * Get compact unit for a certain number of digits
  1242. *
  1243. * @param {number} base The number of digits to get the unit for.
  1244. * @param {string} plurality The plurality of the number.
  1245. * @return {!goog.i18n.NumberFormat.CompactNumberUnit} The compact unit.
  1246. * @private
  1247. */
  1248. goog.i18n.NumberFormat.prototype.getUnitFor_ = function(base, plurality) {
  1249. var table = this.compactStyle_ == goog.i18n.NumberFormat.CompactStyle.SHORT ?
  1250. goog.i18n.CompactNumberFormatSymbols.COMPACT_DECIMAL_SHORT_PATTERN :
  1251. goog.i18n.CompactNumberFormatSymbols.COMPACT_DECIMAL_LONG_PATTERN;
  1252. if (!goog.isDefAndNotNull(table)) {
  1253. table = goog.i18n.CompactNumberFormatSymbols.COMPACT_DECIMAL_SHORT_PATTERN;
  1254. }
  1255. if (base < 3) {
  1256. return goog.i18n.NumberFormat.NULL_UNIT_;
  1257. } else {
  1258. base = Math.min(14, base);
  1259. var patterns = table[Math.pow(10, base)];
  1260. var previousNonNullBase = base - 1;
  1261. while (!patterns && previousNonNullBase >= 3) {
  1262. patterns = table[Math.pow(10, previousNonNullBase)];
  1263. previousNonNullBase--;
  1264. }
  1265. if (!patterns) {
  1266. return goog.i18n.NumberFormat.NULL_UNIT_;
  1267. }
  1268. var pattern = patterns[plurality];
  1269. if (!pattern || pattern == '0') {
  1270. return goog.i18n.NumberFormat.NULL_UNIT_;
  1271. }
  1272. var parts = /([^0]*)(0+)(.*)/.exec(pattern);
  1273. if (!parts) {
  1274. return goog.i18n.NumberFormat.NULL_UNIT_;
  1275. }
  1276. return {
  1277. prefix: parts[1],
  1278. suffix: parts[3],
  1279. divisorBase: (previousNonNullBase + 1) - (parts[2].length - 1)
  1280. };
  1281. }
  1282. };
  1283. /**
  1284. * Get the compact unit divisor, accounting for rounding of the quantity.
  1285. *
  1286. * @param {number} formattingNumber The number to base the formatting on. The
  1287. * unit will be calculated from this number.
  1288. * @param {number} pluralityNumber The number to use for calculating the
  1289. * plurality.
  1290. * @return {!goog.i18n.NumberFormat.CompactNumberUnit} The unit after rounding.
  1291. * @private
  1292. */
  1293. goog.i18n.NumberFormat.prototype.getUnitAfterRounding_ = function(
  1294. formattingNumber, pluralityNumber) {
  1295. if (this.compactStyle_ == goog.i18n.NumberFormat.CompactStyle.NONE) {
  1296. return goog.i18n.NumberFormat.NULL_UNIT_;
  1297. }
  1298. formattingNumber = Math.abs(formattingNumber);
  1299. pluralityNumber = Math.abs(pluralityNumber);
  1300. var initialPlurality = this.pluralForm_(formattingNumber);
  1301. // Compute the exponent from the formattingNumber, to compute the unit.
  1302. var base = formattingNumber <= 1 ? 0 : this.intLog10_(formattingNumber);
  1303. var initialDivisor = this.getUnitFor_(base, initialPlurality).divisorBase;
  1304. // Round both numbers based on the unit used.
  1305. var pluralityAttempt = pluralityNumber / Math.pow(10, initialDivisor);
  1306. var pluralityRounded = this.roundNumber_(pluralityAttempt);
  1307. var formattingAttempt = formattingNumber / Math.pow(10, initialDivisor);
  1308. var formattingRounded = this.roundNumber_(formattingAttempt);
  1309. // Compute the plurality of the pluralityNumber when formatted using the name
  1310. // units as the formattingNumber.
  1311. var finalPlurality =
  1312. this.pluralForm_(pluralityRounded.intValue + pluralityRounded.fracValue);
  1313. // Get the final unit, using the rounded formatting number to get the correct
  1314. // unit, and the plurality computed from the pluralityNumber.
  1315. return this.getUnitFor_(
  1316. initialDivisor + this.intLog10_(formattingRounded.intValue),
  1317. finalPlurality);
  1318. };
  1319. /**
  1320. * Get the integer base 10 logarithm of a number.
  1321. *
  1322. * @param {number} number The number to log.
  1323. * @return {number} The lowest integer n such that 10^n >= number.
  1324. * @private
  1325. */
  1326. goog.i18n.NumberFormat.prototype.intLog10_ = function(number) {
  1327. // Handle infinity.
  1328. if (!isFinite(number)) {
  1329. return number > 0 ? number : 0;
  1330. }
  1331. // Turns out Math.log(1000000)/Math.LN10 is strictly less than 6.
  1332. var i = 0;
  1333. while ((number /= 10) >= 1) i++;
  1334. return i;
  1335. };
  1336. /**
  1337. * Round to a certain number of significant digits.
  1338. *
  1339. * @param {number} number The number to round.
  1340. * @param {number} significantDigits The number of significant digits
  1341. * to round to.
  1342. * @param {number} scale Treat number as fixed point times 10^scale.
  1343. * @return {number} The rounded number.
  1344. * @private
  1345. */
  1346. goog.i18n.NumberFormat.prototype.roundToSignificantDigits_ = function(
  1347. number, significantDigits, scale) {
  1348. if (!number) return number;
  1349. var digits = this.intLog10_(number);
  1350. var magnitude = significantDigits - digits - 1;
  1351. // Only round fraction, not (potentially shifted) integers.
  1352. if (magnitude < -scale) {
  1353. var point = Math.pow(10, scale);
  1354. return Math.round(number / point) * point;
  1355. }
  1356. var power = Math.pow(10, magnitude);
  1357. var shifted = Math.round(number * power);
  1358. return shifted / power;
  1359. };
  1360. /**
  1361. * Get the plural form of a number.
  1362. * @param {number} quantity The quantity to find plurality of.
  1363. * @return {string} One of 'zero', 'one', 'two', 'few', 'many', 'other'.
  1364. * @private
  1365. */
  1366. goog.i18n.NumberFormat.prototype.pluralForm_ = function(quantity) {
  1367. /* TODO: Implement */
  1368. return 'other';
  1369. };
  1370. /**
  1371. * Checks if the currency symbol comes before the value ($12) or after (12$)
  1372. * Handy for applications that need to have separate UI fields for the currency
  1373. * value and symbol, especially for input: Price: [USD] [123.45]
  1374. * The currency symbol might be a combo box, or a label.
  1375. *
  1376. * @return {boolean} true if currency is before value.
  1377. */
  1378. goog.i18n.NumberFormat.prototype.isCurrencyCodeBeforeValue = function() {
  1379. var posCurrSymbol = this.pattern_.indexOf('\u00A4'); // '¤' Currency sign
  1380. var posPound = this.pattern_.indexOf('#');
  1381. var posZero = this.pattern_.indexOf('0');
  1382. // posCurrValue is the first '#' or '0' found.
  1383. // If none of them is found (not possible, but still),
  1384. // the result is true (postCurrSymbol < MAX_VALUE)
  1385. // That is OK, matches the en_US and ROOT locales.
  1386. var posCurrValue = Number.MAX_VALUE;
  1387. if (posPound >= 0 && posPound < posCurrValue) {
  1388. posCurrValue = posPound;
  1389. }
  1390. if (posZero >= 0 && posZero < posCurrValue) {
  1391. posCurrValue = posZero;
  1392. }
  1393. // No need to test, it is guaranteed that both these symbols exist.
  1394. // If not, we have bigger problems than this.
  1395. return posCurrSymbol < posCurrValue;
  1396. };