UNPKG

44.6 kBJavaScriptView Raw
1// 11.1 The Intl.NumberFormat constructor
2// ======================================
3
4import {
5 IsWellFormedCurrencyCode,
6} from "./6.locales-currencies-tz.js";
7
8import {
9 Intl,
10} from "./8.intl.js";
11
12import {
13 CanonicalizeLocaleList,
14 SupportedLocales,
15 ResolveLocale,
16 GetNumberOption,
17 GetOption,
18} from "./9.negotiation.js";
19
20import {
21 internals,
22 log10Floor,
23 List,
24 toObject,
25 arrPush,
26 arrJoin,
27 arrShift,
28 Record,
29 hop,
30 defineProperty,
31 es3,
32 fnBind,
33 getInternalProperties,
34 createRegExpRestore,
35 secret,
36 objCreate,
37} from "./util.js";
38
39// Currency minor units output from get-4217 grunt task, formatted
40const currencyMinorUnits = {
41 BHD: 3, BYR: 0, XOF: 0, BIF: 0, XAF: 0, CLF: 4, CLP: 0, KMF: 0, DJF: 0,
42 XPF: 0, GNF: 0, ISK: 0, IQD: 3, JPY: 0, JOD: 3, KRW: 0, KWD: 3, LYD: 3,
43 OMR: 3, PYG: 0, RWF: 0, TND: 3, UGX: 0, UYI: 0, VUV: 0, VND: 0,
44};
45
46// Define the NumberFormat constructor internally so it cannot be tainted
47export function NumberFormatConstructor () {
48 let locales = arguments[0];
49 let options = arguments[1];
50
51 if (!this || this === Intl) {
52 return new Intl.NumberFormat(locales, options);
53 }
54
55 return InitializeNumberFormat(toObject(this), locales, options);
56}
57
58defineProperty(Intl, 'NumberFormat', {
59 configurable: true,
60 writable: true,
61 value: NumberFormatConstructor,
62});
63
64// Must explicitly set prototypes as unwritable
65defineProperty(Intl.NumberFormat, 'prototype', {
66 writable: false,
67});
68
69/**
70 * The abstract operation InitializeNumberFormat accepts the arguments
71 * numberFormat (which must be an object), locales, and options. It initializes
72 * numberFormat as a NumberFormat object.
73 */
74export function /*11.1.1.1 */InitializeNumberFormat (numberFormat, locales, options) {
75 // This will be a internal properties object if we're not already initialized
76 let internal = getInternalProperties(numberFormat);
77
78 // Create an object whose props can be used to restore the values of RegExp props
79 let regexpState = createRegExpRestore();
80
81 // 1. If numberFormat has an [[initializedIntlObject]] internal property with
82 // value true, throw a TypeError exception.
83 if (internal['[[initializedIntlObject]]'] === true)
84 throw new TypeError('`this` object has already been initialized as an Intl object');
85
86 // Need this to access the `internal` object
87 defineProperty(numberFormat, '__getInternalProperties', {
88 value: function () {
89 // NOTE: Non-standard, for internal use only
90 if (arguments[0] === secret)
91 return internal;
92 },
93 });
94
95 // 2. Set the [[initializedIntlObject]] internal property of numberFormat to true.
96 internal['[[initializedIntlObject]]'] = true;
97
98 // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
99 // abstract operation (defined in 9.2.1) with argument locales.
100 let requestedLocales = CanonicalizeLocaleList(locales);
101
102 // 4. If options is undefined, then
103 if (options === undefined)
104 // a. Let options be the result of creating a new object as if by the
105 // expression new Object() where Object is the standard built-in constructor
106 // with that name.
107 options = {};
108
109 // 5. Else
110 else
111 // a. Let options be ToObject(options).
112 options = toObject(options);
113
114 // 6. Let opt be a new Record.
115 let opt = new Record(),
116
117 // 7. Let matcher be the result of calling the GetOption abstract operation
118 // (defined in 9.2.9) with the arguments options, "localeMatcher", "string",
119 // a List containing the two String values "lookup" and "best fit", and
120 // "best fit".
121 matcher = GetOption(options, 'localeMatcher', 'string', new List('lookup', 'best fit'), 'best fit');
122
123 // 8. Set opt.[[localeMatcher]] to matcher.
124 opt['[[localeMatcher]]'] = matcher;
125
126 // 9. Let NumberFormat be the standard built-in object that is the initial value
127 // of Intl.NumberFormat.
128 // 10. Let localeData be the value of the [[localeData]] internal property of
129 // NumberFormat.
130 let localeData = internals.NumberFormat['[[localeData]]'];
131
132 // 11. Let r be the result of calling the ResolveLocale abstract operation
133 // (defined in 9.2.5) with the [[availableLocales]] internal property of
134 // NumberFormat, requestedLocales, opt, the [[relevantExtensionKeys]]
135 // internal property of NumberFormat, and localeData.
136 let r = ResolveLocale(
137 internals.NumberFormat['[[availableLocales]]'], requestedLocales,
138 opt, internals.NumberFormat['[[relevantExtensionKeys]]'], localeData
139 );
140
141 // 12. Set the [[locale]] internal property of numberFormat to the value of
142 // r.[[locale]].
143 internal['[[locale]]'] = r['[[locale]]'];
144
145 // 13. Set the [[numberingSystem]] internal property of numberFormat to the value
146 // of r.[[nu]].
147 internal['[[numberingSystem]]'] = r['[[nu]]'];
148
149 // The specification doesn't tell us to do this, but it's helpful later on
150 internal['[[dataLocale]]'] = r['[[dataLocale]]'];
151
152 // 14. Let dataLocale be the value of r.[[dataLocale]].
153 let dataLocale = r['[[dataLocale]]'];
154
155 // 15. Let s be the result of calling the GetOption abstract operation with the
156 // arguments options, "style", "string", a List containing the three String
157 // values "decimal", "percent", and "currency", and "decimal".
158 let s = GetOption(options, 'style', 'string', new List('decimal', 'percent', 'currency'), 'decimal');
159
160 // 16. Set the [[style]] internal property of numberFormat to s.
161 internal['[[style]]'] = s;
162
163 // 17. Let c be the result of calling the GetOption abstract operation with the
164 // arguments options, "currency", "string", undefined, and undefined.
165 let c = GetOption(options, 'currency', 'string');
166
167 // 18. If c is not undefined and the result of calling the
168 // IsWellFormedCurrencyCode abstract operation (defined in 6.3.1) with
169 // argument c is false, then throw a RangeError exception.
170 if (c !== undefined && !IsWellFormedCurrencyCode(c))
171 throw new RangeError("'" + c + "' is not a valid currency code");
172
173 // 19. If s is "currency" and c is undefined, throw a TypeError exception.
174 if (s === 'currency' && c === undefined)
175 throw new TypeError('Currency code is required when style is currency');
176
177 let cDigits;
178
179 // 20. If s is "currency", then
180 if (s === 'currency') {
181 // a. Let c be the result of converting c to upper case as specified in 6.1.
182 c = c.toUpperCase();
183
184 // b. Set the [[currency]] internal property of numberFormat to c.
185 internal['[[currency]]'] = c;
186
187 // c. Let cDigits be the result of calling the CurrencyDigits abstract
188 // operation (defined below) with argument c.
189 cDigits = CurrencyDigits(c);
190 }
191
192 // 21. Let cd be the result of calling the GetOption abstract operation with the
193 // arguments options, "currencyDisplay", "string", a List containing the
194 // three String values "code", "symbol", and "name", and "symbol".
195 let cd = GetOption(options, 'currencyDisplay', 'string', new List('code', 'symbol', 'name'), 'symbol');
196
197 // 22. If s is "currency", then set the [[currencyDisplay]] internal property of
198 // numberFormat to cd.
199 if (s === 'currency')
200 internal['[[currencyDisplay]]'] = cd;
201
202 // 23. Let mnid be the result of calling the GetNumberOption abstract operation
203 // (defined in 9.2.10) with arguments options, "minimumIntegerDigits", 1, 21,
204 // and 1.
205 let mnid = GetNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
206
207 // 24. Set the [[minimumIntegerDigits]] internal property of numberFormat to mnid.
208 internal['[[minimumIntegerDigits]]'] = mnid;
209
210 // 25. If s is "currency", then let mnfdDefault be cDigits; else let mnfdDefault
211 // be 0.
212 let mnfdDefault = s === 'currency' ? cDigits : 0;
213
214 // 26. Let mnfd be the result of calling the GetNumberOption abstract operation
215 // with arguments options, "minimumFractionDigits", 0, 20, and mnfdDefault.
216 let mnfd = GetNumberOption(options, 'minimumFractionDigits', 0, 20, mnfdDefault);
217
218 // 27. Set the [[minimumFractionDigits]] internal property of numberFormat to mnfd.
219 internal['[[minimumFractionDigits]]'] = mnfd;
220
221 // 28. If s is "currency", then let mxfdDefault be max(mnfd, cDigits); else if s
222 // is "percent", then let mxfdDefault be max(mnfd, 0); else let mxfdDefault
223 // be max(mnfd, 3).
224 let mxfdDefault = s === 'currency' ? Math.max(mnfd, cDigits)
225 : (s === 'percent' ? Math.max(mnfd, 0) : Math.max(mnfd, 3));
226
227 // 29. Let mxfd be the result of calling the GetNumberOption abstract operation
228 // with arguments options, "maximumFractionDigits", mnfd, 20, and mxfdDefault.
229 let mxfd = GetNumberOption(options, 'maximumFractionDigits', mnfd, 20, mxfdDefault);
230
231 // 30. Set the [[maximumFractionDigits]] internal property of numberFormat to mxfd.
232 internal['[[maximumFractionDigits]]'] = mxfd;
233
234 // 31. Let mnsd be the result of calling the [[Get]] internal method of options
235 // with argument "minimumSignificantDigits".
236 let mnsd = options.minimumSignificantDigits;
237
238 // 32. Let mxsd be the result of calling the [[Get]] internal method of options
239 // with argument "maximumSignificantDigits".
240 let mxsd = options.maximumSignificantDigits;
241
242 // 33. If mnsd is not undefined or mxsd is not undefined, then:
243 if (mnsd !== undefined || mxsd !== undefined) {
244 // a. Let mnsd be the result of calling the GetNumberOption abstract
245 // operation with arguments options, "minimumSignificantDigits", 1, 21,
246 // and 1.
247 mnsd = GetNumberOption(options, 'minimumSignificantDigits', 1, 21, 1);
248
249 // b. Let mxsd be the result of calling the GetNumberOption abstract
250 // operation with arguments options, "maximumSignificantDigits", mnsd,
251 // 21, and 21.
252 mxsd = GetNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
253
254 // c. Set the [[minimumSignificantDigits]] internal property of numberFormat
255 // to mnsd, and the [[maximumSignificantDigits]] internal property of
256 // numberFormat to mxsd.
257 internal['[[minimumSignificantDigits]]'] = mnsd;
258 internal['[[maximumSignificantDigits]]'] = mxsd;
259 }
260 // 34. Let g be the result of calling the GetOption abstract operation with the
261 // arguments options, "useGrouping", "boolean", undefined, and true.
262 let g = GetOption(options, 'useGrouping', 'boolean', undefined, true);
263
264 // 35. Set the [[useGrouping]] internal property of numberFormat to g.
265 internal['[[useGrouping]]'] = g;
266
267 // 36. Let dataLocaleData be the result of calling the [[Get]] internal method of
268 // localeData with argument dataLocale.
269 let dataLocaleData = localeData[dataLocale];
270
271 // 37. Let patterns be the result of calling the [[Get]] internal method of
272 // dataLocaleData with argument "patterns".
273 let patterns = dataLocaleData.patterns;
274
275 // 38. Assert: patterns is an object (see 11.2.3)
276
277 // 39. Let stylePatterns be the result of calling the [[Get]] internal method of
278 // patterns with argument s.
279 let stylePatterns = patterns[s];
280
281 // 40. Set the [[positivePattern]] internal property of numberFormat to the
282 // result of calling the [[Get]] internal method of stylePatterns with the
283 // argument "positivePattern".
284 internal['[[positivePattern]]'] = stylePatterns.positivePattern;
285
286 // 41. Set the [[negativePattern]] internal property of numberFormat to the
287 // result of calling the [[Get]] internal method of stylePatterns with the
288 // argument "negativePattern".
289 internal['[[negativePattern]]'] = stylePatterns.negativePattern;
290
291 // 42. Set the [[boundFormat]] internal property of numberFormat to undefined.
292 internal['[[boundFormat]]'] = undefined;
293
294 // 43. Set the [[initializedNumberFormat]] internal property of numberFormat to
295 // true.
296 internal['[[initializedNumberFormat]]'] = true;
297
298 // In ES3, we need to pre-bind the format() function
299 if (es3)
300 numberFormat.format = GetFormatNumber.call(numberFormat);
301
302 // Restore the RegExp properties
303 regexpState.exp.test(regexpState.input);
304
305 // Return the newly initialised object
306 return numberFormat;
307}
308
309function CurrencyDigits(currency) {
310 // When the CurrencyDigits abstract operation is called with an argument currency
311 // (which must be an upper case String value), the following steps are taken:
312
313 // 1. If the ISO 4217 currency and funds code list contains currency as an
314 // alphabetic code, then return the minor unit value corresponding to the
315 // currency from the list; else return 2.
316 return currencyMinorUnits[currency] !== undefined
317 ? currencyMinorUnits[currency]
318 : 2;
319}
320
321/* 11.2.3 */internals.NumberFormat = {
322 '[[availableLocales]]': [],
323 '[[relevantExtensionKeys]]': ['nu'],
324 '[[localeData]]': {},
325};
326
327/**
328 * When the supportedLocalesOf method of Intl.NumberFormat is called, the
329 * following steps are taken:
330 */
331/* 11.2.2 */
332defineProperty(Intl.NumberFormat, 'supportedLocalesOf', {
333 configurable: true,
334 writable: true,
335 value: fnBind.call(function (locales) {
336 // Bound functions only have the `this` value altered if being used as a constructor,
337 // this lets us imitate a native function that has no constructor
338 if (!hop.call(this, '[[availableLocales]]'))
339 throw new TypeError('supportedLocalesOf() is not a constructor');
340
341 // Create an object whose props can be used to restore the values of RegExp props
342 let regexpState = createRegExpRestore(),
343
344 // 1. If options is not provided, then let options be undefined.
345 options = arguments[1],
346
347 // 2. Let availableLocales be the value of the [[availableLocales]] internal
348 // property of the standard built-in object that is the initial value of
349 // Intl.NumberFormat.
350
351 availableLocales = this['[[availableLocales]]'],
352
353 // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
354 // abstract operation (defined in 9.2.1) with argument locales.
355 requestedLocales = CanonicalizeLocaleList(locales);
356
357 // Restore the RegExp properties
358 regexpState.exp.test(regexpState.input);
359
360 // 4. Return the result of calling the SupportedLocales abstract operation
361 // (defined in 9.2.8) with arguments availableLocales, requestedLocales,
362 // and options.
363 return SupportedLocales(availableLocales, requestedLocales, options);
364 }, internals.NumberFormat),
365});
366
367/**
368 * This named accessor property returns a function that formats a number
369 * according to the effective locale and the formatting options of this
370 * NumberFormat object.
371 */
372/* 11.3.2 */defineProperty(Intl.NumberFormat.prototype, 'format', {
373 configurable: true,
374 get: GetFormatNumber,
375});
376
377function GetFormatNumber() {
378 let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
379
380 // Satisfy test 11.3_b
381 if (!internal || !internal['[[initializedNumberFormat]]'])
382 throw new TypeError('`this` value for format() is not an initialized Intl.NumberFormat object.');
383
384 // The value of the [[Get]] attribute is a function that takes the following
385 // steps:
386
387 // 1. If the [[boundFormat]] internal property of this NumberFormat object
388 // is undefined, then:
389 if (internal['[[boundFormat]]'] === undefined) {
390 // a. Let F be a Function object, with internal properties set as
391 // specified for built-in functions in ES5, 15, or successor, and the
392 // length property set to 1, that takes the argument value and
393 // performs the following steps:
394 let F = function (value) {
395 // i. If value is not provided, then let value be undefined.
396 // ii. Let x be ToNumber(value).
397 // iii. Return the result of calling the FormatNumber abstract
398 // operation (defined below) with arguments this and x.
399 return FormatNumber(this, /* x = */Number(value));
400 };
401
402 // b. Let bind be the standard built-in function object defined in ES5,
403 // 15.3.4.5.
404 // c. Let bf be the result of calling the [[Call]] internal method of
405 // bind with F as the this value and an argument list containing
406 // the single item this.
407 let bf = fnBind.call(F, this);
408
409 // d. Set the [[boundFormat]] internal property of this NumberFormat
410 // object to bf.
411 internal['[[boundFormat]]'] = bf;
412 }
413 // Return the value of the [[boundFormat]] internal property of this
414 // NumberFormat object.
415 return internal['[[boundFormat]]'];
416 }
417
418Intl.NumberFormat.prototype.formatToParts = function(value) {
419 let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
420 if (!internal || !internal['[[initializedNumberFormat]]'])
421 throw new TypeError('`this` value for formatToParts() is not an initialized Intl.NumberFormat object.');
422
423 let x = Number(value);
424 return FormatNumberToParts(this, x);
425};
426
427/*
428 * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html]
429 * @clause[sec-formatnumbertoparts]
430 */
431function FormatNumberToParts(numberFormat, x) {
432 // 1. Let parts be ? PartitionNumberPattern(numberFormat, x).
433 let parts = PartitionNumberPattern(numberFormat, x);
434 // 2. Let result be ArrayCreate(0).
435 let result = [];
436 // 3. Let n be 0.
437 let n = 0;
438 // 4. For each part in parts, do:
439 for (let i = 0; parts.length > i; i++) {
440 let part = parts[i];
441 // a. Let O be ObjectCreate(%ObjectPrototype%).
442 let O = {};
443 // a. Perform ? CreateDataPropertyOrThrow(O, "type", part.[[type]]).
444 O.type = part['[[type]]'];
445 // a. Perform ? CreateDataPropertyOrThrow(O, "value", part.[[value]]).
446 O.value = part['[[value]]'];
447 // a. Perform ? CreateDataPropertyOrThrow(result, ? ToString(n), O).
448 result[n] = O;
449 // a. Increment n by 1.
450 n += 1;
451 }
452 // 5. Return result.
453 return result;
454}
455
456/*
457 * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html]
458 * @clause[sec-partitionnumberpattern]
459 */
460function PartitionNumberPattern(numberFormat, x) {
461
462 let internal = getInternalProperties(numberFormat),
463 locale = internal['[[dataLocale]]'],
464 nums = internal['[[numberingSystem]]'],
465 data = internals.NumberFormat['[[localeData]]'][locale],
466 ild = data.symbols[nums] || data.symbols.latn,
467 pattern;
468
469 // 1. If x is not NaN and x < 0, then:
470 if (!isNaN(x) && x < 0) {
471 // a. Let x be -x.
472 x = -x;
473 // a. Let pattern be the value of numberFormat.[[negativePattern]].
474 pattern = internal['[[negativePattern]]'];
475 }
476 // 2. Else,
477 else {
478 // a. Let pattern be the value of numberFormat.[[positivePattern]].
479 pattern = internal['[[positivePattern]]'];
480 }
481 // 3. Let result be a new empty List.
482 let result = new List();
483 // 4. Let beginIndex be Call(%StringProto_indexOf%, pattern, "{", 0).
484 let beginIndex = pattern.indexOf('{', 0);
485 // 5. Let endIndex be 0.
486 let endIndex = 0;
487 // 6. Let nextIndex be 0.
488 let nextIndex = 0;
489 // 7. Let length be the number of code units in pattern.
490 let length = pattern.length;
491 // 8. Repeat while beginIndex is an integer index into pattern:
492 while (beginIndex > -1 && beginIndex < length) {
493 // a. Set endIndex to Call(%StringProto_indexOf%, pattern, "}", beginIndex)
494 endIndex = pattern.indexOf('}', beginIndex);
495 // a. If endIndex = -1, throw new Error exception.
496 if (endIndex === -1) throw new Error();
497 // a. If beginIndex is greater than nextIndex, then:
498 if (beginIndex > nextIndex) {
499 // i. Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive.
500 let literal = pattern.substring(nextIndex, beginIndex);
501 // ii. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result.
502 arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal });
503 }
504 // a. Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive.
505 let p = pattern.substring(beginIndex + 1, endIndex);
506 // a. If p is equal "number", then:
507 if (p === "number") {
508 // i. If x is NaN,
509 if (isNaN(x)) {
510 // 1. Let n be an ILD String value indicating the NaN value.
511 let n = ild.nan;
512 // 2. Add new part record { [[type]]: "nan", [[value]]: n } as a new element of the list result.
513 arrPush.call(result, { '[[type]]': 'nan', '[[value]]': n });
514 }
515 // ii. Else if isFinite(x) is false,
516 else if (!isFinite(x)) {
517 // 1. Let n be an ILD String value indicating infinity.
518 let n = ild.infinity;
519 // 2. Add new part record { [[type]]: "infinity", [[value]]: n } as a new element of the list result.
520 arrPush.call(result, { '[[type]]': 'infinity', '[[value]]': n });
521 }
522 // iii. Else,
523 else {
524 // 1. If the value of numberFormat.[[style]] is "percent" and isFinite(x), let x be 100 × x.
525 if (internal['[[style]]'] === 'percent' && isFinite(x)) x *= 100;
526
527 let n;
528 // 2. If the numberFormat.[[minimumSignificantDigits]] and numberFormat.[[maximumSignificantDigits]] are present, then
529 if (hop.call(internal, '[[minimumSignificantDigits]]') && hop.call(internal, '[[maximumSignificantDigits]]')) {
530 // a. Let n be ToRawPrecision(x, numberFormat.[[minimumSignificantDigits]], numberFormat.[[maximumSignificantDigits]]).
531 n = ToRawPrecision(x, internal['[[minimumSignificantDigits]]'], internal['[[maximumSignificantDigits]]']);
532 }
533 // 3. Else,
534 else {
535 // a. Let n be ToRawFixed(x, numberFormat.[[minimumIntegerDigits]], numberFormat.[[minimumFractionDigits]], numberFormat.[[maximumFractionDigits]]).
536 n = ToRawFixed(x, internal['[[minimumIntegerDigits]]'], internal['[[minimumFractionDigits]]'], internal['[[maximumFractionDigits]]']);
537 }
538 // 4. If the value of the numberFormat.[[numberingSystem]] matches one of the values in the "Numbering System" column of Table 2 below, then
539 if (numSys[nums]) {
540 // a. Let digits be an array whose 10 String valued elements are the UTF-16 string representations of the 10 digits specified in the "Digits" column of the matching row in Table 2.
541 let digits = numSys[nums];
542 // a. Replace each digit in n with the value of digits[digit].
543 n = String(n).replace(/\d/g, (digit) => {
544 return digits[digit];
545 });
546 }
547 // 5. Else use an implementation dependent algorithm to map n to the appropriate representation of n in the given numbering system.
548 else n = String(n); // ###TODO###
549
550 let integer;
551 let fraction;
552 // 6. Let decimalSepIndex be Call(%StringProto_indexOf%, n, ".", 0).
553 let decimalSepIndex = n.indexOf('.', 0);
554 // 7. If decimalSepIndex > 0, then:
555 if (decimalSepIndex > 0) {
556 // a. Let integer be the substring of n from position 0, inclusive, to position decimalSepIndex, exclusive.
557 integer = n.substring(0, decimalSepIndex);
558 // a. Let fraction be the substring of n from position decimalSepIndex, exclusive, to the end of n.
559 fraction = n.substring(decimalSepIndex + 1, decimalSepIndex.length);
560 }
561 // 8. Else:
562 else {
563 // a. Let integer be n.
564 integer = n;
565 // a. Let fraction be undefined.
566 fraction = undefined;
567 }
568 // 9. If the value of the numberFormat.[[useGrouping]] is true,
569 if (internal['[[useGrouping]]'] === true) {
570 // a. Let groupSepSymbol be the ILND String representing the grouping separator.
571 let groupSepSymbol = ild.group;
572 // a. Let groups be a List whose elements are, in left to right order, the substrings defined by ILND set of locations within the integer.
573 let groups = [];
574 // ----> implementation:
575 // Primary group represents the group closest to the decimal
576 let pgSize = data.patterns.primaryGroupSize || 3;
577 // Secondary group is every other group
578 let sgSize = data.patterns.secondaryGroupSize || pgSize;
579 // Group only if necessary
580 if (integer.length > pgSize) {
581 // Index of the primary grouping separator
582 let end = integer.length - pgSize;
583 // Starting index for our loop
584 let idx = end % sgSize;
585 let start = integer.slice(0, idx);
586 if (start.length) arrPush.call(groups, start);
587 // Loop to separate into secondary grouping digits
588 while (idx < end) {
589 arrPush.call(groups, integer.slice(idx, idx + sgSize));
590 idx += sgSize;
591 }
592 // Add the primary grouping digits
593 arrPush.call(groups, integer.slice(end));
594 } else {
595 arrPush.call(groups, integer);
596 }
597 // a. Assert: The number of elements in groups List is greater than 0.
598 if (groups.length === 0) throw new Error();
599 // a. Repeat, while groups List is not empty:
600 while (groups.length) {
601 // i. Remove the first element from groups and let integerGroup be the value of that element.
602 let integerGroup = arrShift.call(groups);
603 // ii. Add new part record { [[type]]: "integer", [[value]]: integerGroup } as a new element of the list result.
604 arrPush.call(result, { '[[type]]': 'integer', '[[value]]': integerGroup });
605 // iii. If groups List is not empty, then:
606 if (groups.length) {
607 // 1. Add new part record { [[type]]: "group", [[value]]: groupSepSymbol } as a new element of the list result.
608 arrPush.call(result, { '[[type]]': 'group', '[[value]]': groupSepSymbol });
609 }
610 }
611 }
612 // 10. Else,
613 else {
614 // a. Add new part record { [[type]]: "integer", [[value]]: integer } as a new element of the list result.
615 arrPush.call(result, { '[[type]]': 'integer', '[[value]]': integer });
616 }
617 // 11. If fraction is not undefined, then:
618 if (fraction !== undefined) {
619 // a. Let decimalSepSymbol be the ILND String representing the decimal separator.
620 let decimalSepSymbol = ild.decimal;
621 // a. Add new part record { [[type]]: "decimal", [[value]]: decimalSepSymbol } as a new element of the list result.
622 arrPush.call(result, { '[[type]]': 'decimal', '[[value]]': decimalSepSymbol });
623 // a. Add new part record { [[type]]: "fraction", [[value]]: fraction } as a new element of the list result.
624 arrPush.call(result, { '[[type]]': 'fraction', '[[value]]': fraction });
625 }
626 }
627 }
628 // a. Else if p is equal "plusSign", then:
629 else if (p === "plusSign") {
630 // i. Let plusSignSymbol be the ILND String representing the plus sign.
631 let plusSignSymbol = ild.plusSign;
632 // ii. Add new part record { [[type]]: "plusSign", [[value]]: plusSignSymbol } as a new element of the list result.
633 arrPush.call(result, { '[[type]]': 'plusSign', '[[value]]': plusSignSymbol });
634 }
635 // a. Else if p is equal "minusSign", then:
636 else if (p === "minusSign") {
637 // i. Let minusSignSymbol be the ILND String representing the minus sign.
638 let minusSignSymbol = ild.minusSign;
639 // ii. Add new part record { [[type]]: "minusSign", [[value]]: minusSignSymbol } as a new element of the list result.
640 arrPush.call(result, { '[[type]]': 'minusSign', '[[value]]': minusSignSymbol });
641 }
642 // a. Else if p is equal "percentSign" and numberFormat.[[style]] is "percent", then:
643 else if (p === "percentSign" && internal['[[style]]'] === "percent") {
644 // i. Let percentSignSymbol be the ILND String representing the percent sign.
645 let percentSignSymbol = ild.percentSign;
646 // ii. Add new part record { [[type]]: "percentSign", [[value]]: percentSignSymbol } as a new element of the list result.
647 arrPush.call(result, { '[[type]]': 'literal', '[[value]]': percentSignSymbol });
648 }
649 // a. Else if p is equal "currency" and numberFormat.[[style]] is "currency", then:
650 else if (p === "currency" && internal['[[style]]'] === "currency") {
651 // i. Let currency be the value of numberFormat.[[currency]].
652 let currency = internal['[[currency]]'];
653
654 let cd;
655
656 // ii. If numberFormat.[[currencyDisplay]] is "code", then
657 if (internal['[[currencyDisplay]]'] === "code") {
658 // 1. Let cd be currency.
659 cd = currency;
660 }
661 // iii. Else if numberFormat.[[currencyDisplay]] is "symbol", then
662 else if (internal['[[currencyDisplay]]'] === "symbol") {
663 // 1. Let cd be an ILD string representing currency in short form. If the implementation does not have such a representation of currency, use currency itself.
664 cd = data.currencies[currency] || currency;
665 }
666 // iv. Else if numberFormat.[[currencyDisplay]] is "name", then
667 else if (internal['[[currencyDisplay]]'] === "name") {
668 // 1. Let cd be an ILD string representing currency in long form. If the implementation does not have such a representation of currency, then use currency itself.
669 cd = currency;
670 }
671 // v. Add new part record { [[type]]: "currency", [[value]]: cd } as a new element of the list result.
672 arrPush.call(result, { '[[type]]': 'currency', '[[value]]': cd });
673 }
674 // a. Else,
675 else {
676 // i. Let literal be the substring of pattern from position beginIndex, inclusive, to position endIndex, inclusive.
677 let literal = pattern.substring(beginIndex, endIndex);
678 // ii. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result.
679 arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal });
680 }
681 // a. Set nextIndex to endIndex + 1.
682 nextIndex = endIndex + 1;
683 // a. Set beginIndex to Call(%StringProto_indexOf%, pattern, "{", nextIndex)
684 beginIndex = pattern.indexOf('{', nextIndex);
685 }
686 // 9. If nextIndex is less than length, then:
687 if (nextIndex < length) {
688 // a. Let literal be the substring of pattern from position nextIndex, inclusive, to position length, exclusive.
689 let literal = pattern.substring(nextIndex, length);
690 // a. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result.
691 arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal });
692 }
693 // 10. Return result.
694 return result;
695}
696
697/*
698 * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html]
699 * @clause[sec-formatnumber]
700 */
701export function FormatNumber(numberFormat, x) {
702 // 1. Let parts be ? PartitionNumberPattern(numberFormat, x).
703 let parts = PartitionNumberPattern(numberFormat, x);
704 // 2. Let result be an empty String.
705 let result = '';
706 // 3. For each part in parts, do:
707 for (let i = 0; parts.length > i; i++) {
708 let part = parts[i];
709 // a. Set result to a String value produced by concatenating result and part.[[value]].
710 result += part['[[value]]'];
711 }
712 // 4. Return result.
713 return result;
714}
715
716/**
717 * When the ToRawPrecision abstract operation is called with arguments x (which
718 * must be a finite non-negative number), minPrecision, and maxPrecision (both
719 * must be integers between 1 and 21) the following steps are taken:
720 */
721function ToRawPrecision (x, minPrecision, maxPrecision) {
722 // 1. Let p be maxPrecision.
723 let p = maxPrecision;
724
725 let m, e;
726
727 // 2. If x = 0, then
728 if (x === 0) {
729 // a. Let m be the String consisting of p occurrences of the character "0".
730 m = arrJoin.call(Array (p + 1), '0');
731 // b. Let e be 0.
732 e = 0;
733 }
734 // 3. Else
735 else {
736 // a. Let e and n be integers such that 10ᵖ⁻¹ ≤ n < 10ᵖ and for which the
737 // exact mathematical value of n × 10ᵉ⁻ᵖ⁺¹ – x is as close to zero as
738 // possible. If there are two such sets of e and n, pick the e and n for
739 // which n × 10ᵉ⁻ᵖ⁺¹ is larger.
740 e = log10Floor(Math.abs(x));
741
742 // Easier to get to m from here
743 let f = Math.round(Math.exp((Math.abs(e - p + 1)) * Math.LN10));
744
745 // b. Let m be the String consisting of the digits of the decimal
746 // representation of n (in order, with no leading zeroes)
747 m = String(Math.round(e - p + 1 < 0 ? x * f : x / f));
748 }
749
750 // 4. If e ≥ p, then
751 if (e >= p)
752 // a. Return the concatenation of m and e-p+1 occurrences of the character "0".
753 return m + arrJoin.call(Array(e-p+1 + 1), '0');
754
755 // 5. If e = p-1, then
756 else if (e === p - 1)
757 // a. Return m.
758 return m;
759
760 // 6. If e ≥ 0, then
761 else if (e >= 0)
762 // a. Let m be the concatenation of the first e+1 characters of m, the character
763 // ".", and the remaining p–(e+1) characters of m.
764 m = m.slice(0, e + 1) + '.' + m.slice(e + 1);
765
766 // 7. If e < 0, then
767 else if (e < 0)
768 // a. Let m be the concatenation of the String "0.", –(e+1) occurrences of the
769 // character "0", and the string m.
770 m = '0.' + arrJoin.call(Array (-(e+1) + 1), '0') + m;
771
772 // 8. If m contains the character ".", and maxPrecision > minPrecision, then
773 if (m.indexOf(".") >= 0 && maxPrecision > minPrecision) {
774 // a. Let cut be maxPrecision – minPrecision.
775 let cut = maxPrecision - minPrecision;
776
777 // b. Repeat while cut > 0 and the last character of m is "0":
778 while (cut > 0 && m.charAt(m.length-1) === '0') {
779 // i. Remove the last character from m.
780 m = m.slice(0, -1);
781
782 // ii. Decrease cut by 1.
783 cut--;
784 }
785
786 // c. If the last character of m is ".", then
787 if (m.charAt(m.length-1) === '.')
788 // i. Remove the last character from m.
789 m = m.slice(0, -1);
790 }
791 // 9. Return m.
792 return m;
793}
794
795/**
796 * @spec[tc39/ecma402/master/spec/numberformat.html]
797 * @clause[sec-torawfixed]
798 * When the ToRawFixed abstract operation is called with arguments x (which must
799 * be a finite non-negative number), minInteger (which must be an integer between
800 * 1 and 21), minFraction, and maxFraction (which must be integers between 0 and
801 * 20) the following steps are taken:
802 */
803function ToRawFixed(x, minInteger, minFraction, maxFraction) {
804 // 1. Let f be maxFraction.
805 let f = maxFraction;
806 // 2. Let n be an integer for which the exact mathematical value of n ÷ 10f – x is as close to zero as possible. If there are two such n, pick the larger n.
807 let n = Math.pow(10, f) * x; // diverging...
808 // 3. If n = 0, let m be the String "0". Otherwise, let m be the String consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
809 let m = (n === 0 ? "0" : n.toFixed(0)); // divering...
810
811 {
812 // this diversion is needed to take into consideration big numbers, e.g.:
813 // 1.2344501e+37 -> 12344501000000000000000000000000000000
814 let idx;
815 let exp = (idx = m.indexOf('e')) > -1 ? m.slice(idx + 1) : 0;
816 if (exp) {
817 m = m.slice(0, idx).replace('.', '');
818 m += arrJoin.call(Array(exp - (m.length - 1) + 1), '0');
819 }
820 }
821
822 let int;
823 // 4. If f ≠ 0, then
824 if (f !== 0) {
825 // a. Let k be the number of characters in m.
826 let k = m.length;
827 // a. If k ≤ f, then
828 if (k <= f) {
829 // i. Let z be the String consisting of f+1–k occurrences of the character "0".
830 let z = arrJoin.call(Array(f + 1 - k + 1), '0');
831 // ii. Let m be the concatenation of Strings z and m.
832 m = z + m;
833 // iii. Let k be f+1.
834 k = f + 1;
835 }
836 // a. Let a be the first k–f characters of m, and let b be the remaining f characters of m.
837 let a = m.substring(0, k - f), b = m.substring(k - f, m.length);
838 // a. Let m be the concatenation of the three Strings a, ".", and b.
839 m = a + "." + b;
840 // a. Let int be the number of characters in a.
841 int = a.length;
842 }
843 // 5. Else, let int be the number of characters in m.
844 else int = m.length;
845 // 6. Let cut be maxFraction – minFraction.
846 let cut = maxFraction - minFraction;
847 // 7. Repeat while cut > 0 and the last character of m is "0":
848 while (cut > 0 && m.slice(-1) === "0") {
849 // a. Remove the last character from m.
850 m = m.slice(0, -1);
851 // a. Decrease cut by 1.
852 cut--;
853 }
854 // 8. If the last character of m is ".", then
855 if (m.slice(-1) === ".") {
856 // a. Remove the last character from m.
857 m = m.slice(0, -1);
858 }
859 // 9. If int < minInteger, then
860 if (int < minInteger) {
861 // a. Let z be the String consisting of minInteger–int occurrences of the character "0".
862 let z = arrJoin.call(Array(minInteger - int + 1), '0');
863 // a. Let m be the concatenation of Strings z and m.
864 m = z + m;
865 }
866 // 10. Return m.
867 return m;
868}
869
870// Sect 11.3.2 Table 2, Numbering systems
871// ======================================
872let numSys = {
873 arab: ['\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668', '\u0669'],
874 arabext: ['\u06F0', '\u06F1', '\u06F2', '\u06F3', '\u06F4', '\u06F5', '\u06F6', '\u06F7', '\u06F8', '\u06F9'],
875 bali: ['\u1B50', '\u1B51', '\u1B52', '\u1B53', '\u1B54', '\u1B55', '\u1B56', '\u1B57', '\u1B58', '\u1B59'],
876 beng: ['\u09E6', '\u09E7', '\u09E8', '\u09E9', '\u09EA', '\u09EB', '\u09EC', '\u09ED', '\u09EE', '\u09EF'],
877 deva: ['\u0966', '\u0967', '\u0968', '\u0969', '\u096A', '\u096B', '\u096C', '\u096D', '\u096E', '\u096F'],
878 fullwide: ['\uFF10', '\uFF11', '\uFF12', '\uFF13', '\uFF14', '\uFF15', '\uFF16', '\uFF17', '\uFF18', '\uFF19'],
879 gujr: ['\u0AE6', '\u0AE7', '\u0AE8', '\u0AE9', '\u0AEA', '\u0AEB', '\u0AEC', '\u0AED', '\u0AEE', '\u0AEF'],
880 guru: ['\u0A66', '\u0A67', '\u0A68', '\u0A69', '\u0A6A', '\u0A6B', '\u0A6C', '\u0A6D', '\u0A6E', '\u0A6F'],
881 hanidec: ['\u3007', '\u4E00', '\u4E8C', '\u4E09', '\u56DB', '\u4E94', '\u516D', '\u4E03', '\u516B', '\u4E5D'],
882 khmr: ['\u17E0', '\u17E1', '\u17E2', '\u17E3', '\u17E4', '\u17E5', '\u17E6', '\u17E7', '\u17E8', '\u17E9'],
883 knda: ['\u0CE6', '\u0CE7', '\u0CE8', '\u0CE9', '\u0CEA', '\u0CEB', '\u0CEC', '\u0CED', '\u0CEE', '\u0CEF'],
884 laoo: ['\u0ED0', '\u0ED1', '\u0ED2', '\u0ED3', '\u0ED4', '\u0ED5', '\u0ED6', '\u0ED7', '\u0ED8', '\u0ED9'],
885 latn: ['\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039'],
886 limb: ['\u1946', '\u1947', '\u1948', '\u1949', '\u194A', '\u194B', '\u194C', '\u194D', '\u194E', '\u194F'],
887 mlym: ['\u0D66', '\u0D67', '\u0D68', '\u0D69', '\u0D6A', '\u0D6B', '\u0D6C', '\u0D6D', '\u0D6E', '\u0D6F'],
888 mong: ['\u1810', '\u1811', '\u1812', '\u1813', '\u1814', '\u1815', '\u1816', '\u1817', '\u1818', '\u1819'],
889 mymr: ['\u1040', '\u1041', '\u1042', '\u1043', '\u1044', '\u1045', '\u1046', '\u1047', '\u1048', '\u1049'],
890 orya: ['\u0B66', '\u0B67', '\u0B68', '\u0B69', '\u0B6A', '\u0B6B', '\u0B6C', '\u0B6D', '\u0B6E', '\u0B6F'],
891 tamldec: ['\u0BE6', '\u0BE7', '\u0BE8', '\u0BE9', '\u0BEA', '\u0BEB', '\u0BEC', '\u0BED', '\u0BEE', '\u0BEF'],
892 telu: ['\u0C66', '\u0C67', '\u0C68', '\u0C69', '\u0C6A', '\u0C6B', '\u0C6C', '\u0C6D', '\u0C6E', '\u0C6F'],
893 thai: ['\u0E50', '\u0E51', '\u0E52', '\u0E53', '\u0E54', '\u0E55', '\u0E56', '\u0E57', '\u0E58', '\u0E59'],
894 tibt: ['\u0F20', '\u0F21', '\u0F22', '\u0F23', '\u0F24', '\u0F25', '\u0F26', '\u0F27', '\u0F28', '\u0F29'],
895};
896
897/**
898 * This function provides access to the locale and formatting options computed
899 * during initialization of the object.
900 *
901 * The function returns a new object whose properties and attributes are set as
902 * if constructed by an object literal assigning to each of the following
903 * properties the value of the corresponding internal property of this
904 * NumberFormat object (see 11.4): locale, numberingSystem, style, currency,
905 * currencyDisplay, minimumIntegerDigits, minimumFractionDigits,
906 * maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits, and
907 * useGrouping. Properties whose corresponding internal properties are not present
908 * are not assigned.
909 */
910/* 11.3.3 */defineProperty(Intl.NumberFormat.prototype, 'resolvedOptions', {
911 configurable: true,
912 writable: true,
913 value: function () {
914 let prop,
915 descs = new Record(),
916 props = [
917 'locale', 'numberingSystem', 'style', 'currency', 'currencyDisplay',
918 'minimumIntegerDigits', 'minimumFractionDigits', 'maximumFractionDigits',
919 'minimumSignificantDigits', 'maximumSignificantDigits', 'useGrouping',
920 ],
921 internal = this !== null && typeof this === 'object' && getInternalProperties(this);
922
923 // Satisfy test 11.3_b
924 if (!internal || !internal['[[initializedNumberFormat]]'])
925 throw new TypeError('`this` value for resolvedOptions() is not an initialized Intl.NumberFormat object.');
926
927 for (let i = 0, max = props.length; i < max; i++) {
928 if (hop.call(internal, prop = '[['+ props[i] +']]'))
929 descs[props[i]] = { value: internal[prop], writable: true, configurable: true, enumerable: true };
930 }
931
932 return objCreate({}, descs);
933 },
934});