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