NumberFormat.js (34397B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* Portions Copyright Norbert Lindenberg 2011-2012. */ 6 7 #include "NumberingSystemsGenerated.h" 8 9 /** 10 * NumberFormat internal properties. 11 * 12 * 9.1 Internal slots of Service Constructors 13 * 15.2.3 Properties of the Intl.NumberFormat Constructor, Internal slots 14 * 15 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 16 */ 17 var numberFormatInternalProperties = { 18 localeData: numberFormatLocaleData, 19 relevantExtensionKeys: ["nu"], 20 }; 21 22 /** 23 * 15.1.1 Intl.NumberFormat ( [ locales [ , options ] ] ) 24 * 25 * Compute an internal properties object from |lazyNumberFormatData|. 26 * 27 * ES2025 Intl draft rev 5ea95f8a98d660e94c177d6f5e88c6d2962123b1 28 */ 29 function resolveNumberFormatInternals(lazyNumberFormatData) { 30 assert(IsObject(lazyNumberFormatData), "lazy data not an object?"); 31 32 var internalProps = std_Object_create(null); 33 34 var NumberFormat = numberFormatInternalProperties; 35 36 // Compute effective locale. 37 38 // Step 11. 39 var r = ResolveLocale( 40 "NumberFormat", 41 lazyNumberFormatData.requestedLocales, 42 lazyNumberFormatData.opt, 43 NumberFormat.relevantExtensionKeys, 44 NumberFormat.localeData 45 ); 46 47 // Steps 12-14. (Step 13 is not relevant to our implementation.) 48 internalProps.locale = r.locale; 49 internalProps.numberingSystem = r.nu; 50 51 // Compute formatting options. 52 53 // Step 15. SetNumberFormatUnitOptions, step 2. 54 var style = lazyNumberFormatData.style; 55 internalProps.style = style; 56 57 // Step 15. SetNumberFormatUnitOptions, step 12. 58 if (style === "currency") { 59 internalProps.currency = lazyNumberFormatData.currency; 60 internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay; 61 internalProps.currencySign = lazyNumberFormatData.currencySign; 62 } 63 64 // Step 15. SetNumberFormatUnitOptions, step 13. 65 if (style === "unit") { 66 internalProps.unit = lazyNumberFormatData.unit; 67 internalProps.unitDisplay = lazyNumberFormatData.unitDisplay; 68 } 69 70 // Step 18. 71 var notation = lazyNumberFormatData.notation; 72 internalProps.notation = notation; 73 74 // Step 21. SetNumberFormatDigitOptions, step 6. 75 internalProps.minimumIntegerDigits = 76 lazyNumberFormatData.minimumIntegerDigits; 77 78 // Step 21. SetNumberFormatDigitOptions, step 14. 79 internalProps.roundingIncrement = lazyNumberFormatData.roundingIncrement; 80 81 // Step 21. SetNumberFormatDigitOptions, step 15. 82 internalProps.roundingMode = lazyNumberFormatData.roundingMode; 83 84 // Step 21. SetNumberFormatDigitOptions, step 16. 85 internalProps.trailingZeroDisplay = lazyNumberFormatData.trailingZeroDisplay; 86 87 // Step 21. SetNumberFormatDigitOptions, steps 25-26. 88 if ("minimumFractionDigits" in lazyNumberFormatData) { 89 // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the 90 // actual presence (versus undefined-ness) of these properties. 91 assert( 92 "maximumFractionDigits" in lazyNumberFormatData, 93 "min/max frac digits mismatch" 94 ); 95 internalProps.minimumFractionDigits = 96 lazyNumberFormatData.minimumFractionDigits; 97 internalProps.maximumFractionDigits = 98 lazyNumberFormatData.maximumFractionDigits; 99 } 100 101 // Step 21. SetNumberFormatDigitOptions, steps 24 and 26. 102 if ("minimumSignificantDigits" in lazyNumberFormatData) { 103 // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the 104 // actual presence (versus undefined-ness) of these properties. 105 assert( 106 "maximumSignificantDigits" in lazyNumberFormatData, 107 "min/max sig digits mismatch" 108 ); 109 internalProps.minimumSignificantDigits = 110 lazyNumberFormatData.minimumSignificantDigits; 111 internalProps.maximumSignificantDigits = 112 lazyNumberFormatData.maximumSignificantDigits; 113 } 114 115 // Step 21. SetNumberFormatDigitOptions, steps 26-30. 116 internalProps.roundingPriority = lazyNumberFormatData.roundingPriority; 117 118 // Step 24. 119 if (notation === "compact") { 120 internalProps.compactDisplay = lazyNumberFormatData.compactDisplay; 121 } 122 123 // Step 29. 124 internalProps.useGrouping = lazyNumberFormatData.useGrouping; 125 126 // Step 31. 127 internalProps.signDisplay = lazyNumberFormatData.signDisplay; 128 129 // The caller is responsible for associating |internalProps| with the right 130 // object using |setInternalProperties|. 131 return internalProps; 132 } 133 134 /** 135 * Returns an object containing the NumberFormat internal properties of |obj|. 136 */ 137 function getNumberFormatInternals(obj) { 138 assert(IsObject(obj), "getNumberFormatInternals called with non-object"); 139 assert( 140 intl_GuardToNumberFormat(obj) !== null, 141 "getNumberFormatInternals called with non-NumberFormat" 142 ); 143 144 var internals = getIntlObjectInternals(obj); 145 assert( 146 internals.type === "NumberFormat", 147 "bad type escaped getIntlObjectInternals" 148 ); 149 150 // If internal properties have already been computed, use them. 151 var internalProps = maybeInternalProperties(internals); 152 if (internalProps) { 153 return internalProps; 154 } 155 156 // Otherwise it's time to fully create them. 157 internalProps = resolveNumberFormatInternals(internals.lazyData); 158 setInternalProperties(internals, internalProps); 159 return internalProps; 160 } 161 162 /** 163 * 15.5.10 UnwrapNumberFormat ( nf ) 164 * 165 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 166 */ 167 function UnwrapNumberFormat(nf) { 168 // Steps 1-3 (error handling moved to caller). 169 if ( 170 IsObject(nf) && 171 intl_GuardToNumberFormat(nf) === null && 172 !intl_IsWrappedNumberFormat(nf) && 173 callFunction( 174 std_Object_isPrototypeOf, 175 GetBuiltinPrototype("NumberFormat"), 176 nf 177 ) 178 ) { 179 return nf[intlFallbackSymbol()]; 180 } 181 return nf; 182 } 183 184 /* eslint-disable complexity */ 185 /** 186 * 15.1.3 SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault, notation ) 187 * 188 * Applies digit options used for number formatting onto the intl object. 189 * 190 * ES2024 Intl draft rev a1db4567870dbe505121a4255f1210338757190a 191 */ 192 function SetNumberFormatDigitOptions( 193 lazyData, 194 options, 195 mnfdDefault, 196 mxfdDefault, 197 notation 198 ) { 199 assert(IsObject(options), "SetNumberFormatDigitOptions"); 200 assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions"); 201 assert(typeof mxfdDefault === "number", "SetNumberFormatDigitOptions"); 202 assert(mnfdDefault <= mxfdDefault, "SetNumberFormatDigitOptions"); 203 assert(typeof notation === "string", "SetNumberFormatDigitOptions"); 204 205 // Steps 1-5. 206 var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); 207 var mnfd = options.minimumFractionDigits; 208 var mxfd = options.maximumFractionDigits; 209 var mnsd = options.minimumSignificantDigits; 210 var mxsd = options.maximumSignificantDigits; 211 212 // Step 6. 213 lazyData.minimumIntegerDigits = mnid; 214 215 // Step 7. 216 var roundingIncrement = GetNumberOption( 217 options, 218 "roundingIncrement", 219 1, 220 5000, 221 1 222 ); 223 224 // Step 8. 225 switch (roundingIncrement) { 226 case 1: 227 case 2: 228 case 5: 229 case 10: 230 case 20: 231 case 25: 232 case 50: 233 case 100: 234 case 200: 235 case 250: 236 case 500: 237 case 1000: 238 case 2000: 239 case 2500: 240 case 5000: 241 break; 242 default: 243 ThrowRangeError( 244 JSMSG_INVALID_OPTION_VALUE, 245 "roundingIncrement", 246 roundingIncrement 247 ); 248 } 249 250 // Step 9. 251 var roundingMode = GetOption( 252 options, 253 "roundingMode", 254 "string", 255 [ 256 "ceil", 257 "floor", 258 "expand", 259 "trunc", 260 "halfCeil", 261 "halfFloor", 262 "halfExpand", 263 "halfTrunc", 264 "halfEven", 265 ], 266 "halfExpand" 267 ); 268 269 // Step 10. 270 var roundingPriority = GetOption( 271 options, 272 "roundingPriority", 273 "string", 274 ["auto", "morePrecision", "lessPrecision"], 275 "auto" 276 ); 277 278 // Step 11. 279 var trailingZeroDisplay = GetOption( 280 options, 281 "trailingZeroDisplay", 282 "string", 283 ["auto", "stripIfInteger"], 284 "auto" 285 ); 286 287 // Step 12. (This step is a note.) 288 289 // Step 13. 290 if (roundingIncrement !== 1) { 291 mxfdDefault = mnfdDefault; 292 } 293 294 // Step 14. 295 lazyData.roundingIncrement = roundingIncrement; 296 297 // Step 15. 298 lazyData.roundingMode = roundingMode; 299 300 // Step 16. 301 lazyData.trailingZeroDisplay = trailingZeroDisplay; 302 303 // Step 17. 304 var hasSignificantDigits = mnsd !== undefined || mxsd !== undefined; 305 306 // Step 28. 307 var hasFractionDigits = mnfd !== undefined || mxfd !== undefined; 308 309 // Steps 19 and 21.a. 310 var needSignificantDigits = 311 roundingPriority !== "auto" || hasSignificantDigits; 312 313 // Steps 20 and 21.b.i. 314 var needFractionalDigits = 315 roundingPriority !== "auto" || 316 !(hasSignificantDigits || (!hasFractionDigits && notation === "compact")); 317 318 // Step 22. 319 if (needSignificantDigits) { 320 // Step 22.a. 321 if (hasSignificantDigits) { 322 // Step 22.a.i. 323 mnsd = DefaultNumberOption(mnsd, 1, 21, 1); 324 lazyData.minimumSignificantDigits = mnsd; 325 326 // Step 22.a.ii. 327 mxsd = DefaultNumberOption(mxsd, mnsd, 21, 21); 328 lazyData.maximumSignificantDigits = mxsd; 329 } else { 330 // Step 22.b.i. 331 lazyData.minimumSignificantDigits = 1; 332 333 // Step 22.b.ii. 334 lazyData.maximumSignificantDigits = 21; 335 } 336 } 337 338 // Step 23. 339 if (needFractionalDigits) { 340 // Step 23.a. 341 if (hasFractionDigits) { 342 // Step 23.a.i. 343 mnfd = DefaultNumberOption(mnfd, 0, 100, undefined); 344 345 // Step 23.a.ii. 346 mxfd = DefaultNumberOption(mxfd, 0, 100, undefined); 347 348 // Step 23.a.iii. 349 if (mnfd === undefined) { 350 assert( 351 mxfd !== undefined, 352 "mxfd isn't undefined when mnfd is undefined" 353 ); 354 mnfd = std_Math_min(mnfdDefault, mxfd); 355 } 356 357 // Step 23.a.iv. 358 else if (mxfd === undefined) { 359 mxfd = std_Math_max(mxfdDefault, mnfd); 360 } 361 362 // Step 23.a.v. 363 else if (mnfd > mxfd) { 364 ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, mxfd); 365 } 366 367 // Step 23.a.vi. 368 lazyData.minimumFractionDigits = mnfd; 369 370 // Step 23.a.vii. 371 lazyData.maximumFractionDigits = mxfd; 372 } else { 373 // Step 23.b.i. 374 lazyData.minimumFractionDigits = mnfdDefault; 375 376 // Step 23.b.ii. 377 lazyData.maximumFractionDigits = mxfdDefault; 378 } 379 } 380 381 // Steps 24-28. 382 if (!needSignificantDigits && !needFractionalDigits) { 383 assert(!hasSignificantDigits, "bad significant digits in fallback case"); 384 assert( 385 roundingPriority === "auto", 386 `bad rounding in fallback case: ${roundingPriority}` 387 ); 388 assert( 389 notation === "compact", 390 `bad notation in fallback case: ${notation}` 391 ); 392 393 // Steps 24.a-f. 394 lazyData.minimumFractionDigits = 0; 395 lazyData.maximumFractionDigits = 0; 396 lazyData.minimumSignificantDigits = 1; 397 lazyData.maximumSignificantDigits = 2; 398 lazyData.roundingPriority = "morePrecision"; 399 } else { 400 // Steps 25-28. 401 // 402 // Our implementation stores |roundingPriority| instead of using 403 // [[RoundingType]]. 404 lazyData.roundingPriority = roundingPriority; 405 } 406 407 // Step 29. 408 if (roundingIncrement !== 1) { 409 // Step 29.a. 410 // 411 // [[RoundingType]] is `fractionDigits` if |roundingPriority| is equal to 412 // "auto" and |hasSignificantDigits| is false. 413 if (roundingPriority !== "auto") { 414 ThrowTypeError( 415 JSMSG_INVALID_NUMBER_OPTION, 416 "roundingIncrement", 417 "roundingPriority" 418 ); 419 } 420 if (hasSignificantDigits) { 421 ThrowTypeError( 422 JSMSG_INVALID_NUMBER_OPTION, 423 "roundingIncrement", 424 "minimumSignificantDigits" 425 ); 426 } 427 428 // Step 29.b. 429 // 430 // Minimum and maximum fraction digits must be equal. 431 if ( 432 lazyData.minimumFractionDigits !== 433 lazyData.maximumFractionDigits 434 ) { 435 ThrowRangeError(JSMSG_UNEQUAL_FRACTION_DIGITS); 436 } 437 } 438 } 439 /* eslint-enable complexity */ 440 441 /** 442 * Convert s to upper case, but limited to characters a-z. 443 * 444 * Spec: ECMAScript Internationalization API Specification, 6.1. 445 */ 446 function toASCIIUpperCase(s) { 447 assert(typeof s === "string", "toASCIIUpperCase"); 448 449 // String.prototype.toUpperCase may map non-ASCII characters into ASCII, 450 // so go character by character (actually code unit by code unit, but 451 // since we only care about ASCII characters here, that's OK). 452 var result = ""; 453 for (var i = 0; i < s.length; i++) { 454 var c = callFunction(std_String_charCodeAt, s, i); 455 result += 456 0x61 <= c && c <= 0x7a 457 ? callFunction(std_String_fromCharCode, null, c & ~0x20) 458 : s[i]; 459 } 460 return result; 461 } 462 463 /** 464 * 6.3.1 IsWellFormedCurrencyCode ( currency ) 465 * 466 * Verifies that the given string is a well-formed ISO 4217 currency code. 467 * 468 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 469 */ 470 function IsWellFormedCurrencyCode(currency) { 471 assert(typeof currency === "string", "currency is a string value"); 472 473 return currency.length === 3 && IsASCIIAlphaString(currency); 474 } 475 476 /** 477 * 6.6.1 IsWellFormedUnitIdentifier ( unitIdentifier ) 478 * 479 * Verifies that the given string is a well-formed core unit identifier as 480 * defined in UTS #35, Part 2, Section 6. In addition to obeying the UTS #35 481 * core unit identifier syntax, |unitIdentifier| must be one of the identifiers 482 * sanctioned by UTS #35 or be a compound unit composed of two sanctioned simple 483 * units. 484 * 485 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 486 */ 487 function IsWellFormedUnitIdentifier(unitIdentifier) { 488 assert( 489 typeof unitIdentifier === "string", 490 "unitIdentifier is a string value" 491 ); 492 493 // Step 1. 494 if (IsSanctionedSimpleUnitIdentifier(unitIdentifier)) { 495 return true; 496 } 497 498 // Steps 2-3. 499 var pos = callFunction(std_String_indexOf, unitIdentifier, "-per-"); 500 if (pos < 0) { 501 return false; 502 } 503 504 // Step 4. 505 // 506 // Sanctioned single unit identifiers don't include the substring "-per-", 507 // so we can skip searching for the second "-per-" substring. 508 509 var next = pos + "-per-".length; 510 511 // Steps 5-6. 512 var numerator = Substring(unitIdentifier, 0, pos); 513 var denominator = Substring( 514 unitIdentifier, 515 next, 516 unitIdentifier.length - next 517 ); 518 519 // Steps 7-8. 520 return ( 521 IsSanctionedSimpleUnitIdentifier(numerator) && 522 IsSanctionedSimpleUnitIdentifier(denominator) 523 ); 524 } 525 526 #if DEBUG || MOZ_SYSTEM_ICU 527 var availableMeasurementUnits = { 528 value: null, 529 }; 530 #endif 531 532 /** 533 * 6.6.2 IsSanctionedSingleUnitIdentifier ( unitIdentifier ) 534 * 535 * Verifies that the given string is a sanctioned simple core unit identifier. 536 * 537 * Also see: https://unicode.org/reports/tr35/tr35-general.html#Unit_Elements 538 * 539 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 540 */ 541 function IsSanctionedSimpleUnitIdentifier(unitIdentifier) { 542 assert( 543 typeof unitIdentifier === "string", 544 "unitIdentifier is a string value" 545 ); 546 547 var isSanctioned = hasOwn(unitIdentifier, sanctionedSimpleUnitIdentifiers); 548 549 #if DEBUG || MOZ_SYSTEM_ICU 550 if (isSanctioned) { 551 if (availableMeasurementUnits.value === null) { 552 availableMeasurementUnits.value = intl_availableMeasurementUnits(); 553 } 554 555 var isSupported = hasOwn(unitIdentifier, availableMeasurementUnits.value); 556 557 #if MOZ_SYSTEM_ICU 558 // A system ICU may support fewer measurement units, so we need to make 559 // sure the unit is actually supported. 560 isSanctioned = isSupported; 561 #else 562 // Otherwise just assert that the sanctioned unit is also supported. 563 assert( 564 isSupported, 565 `"${unitIdentifier}" is sanctioned but not supported. Did you forget to update 566 intl/icu/data_filter.json to include the unit (and any implicit compound units)? 567 For example "speed/kilometer-per-hour" is implied by "length/kilometer" and 568 "duration/hour" and must therefore also be present.` 569 ); 570 #endif 571 } 572 #endif 573 574 return isSanctioned; 575 } 576 577 /* eslint-disable complexity */ 578 /** 579 * 15.1.1 Intl.NumberFormat ( [ locales [ , options ] ] ) 580 * 581 * Initializes an object as a NumberFormat. 582 * 583 * This method is complicated a moderate bit by its implementing initialization 584 * as a *lazy* concept. Everything that must happen now, does -- but we defer 585 * all the work we can until the object is actually used as a NumberFormat. 586 * This later work occurs in |resolveNumberFormatInternals|; steps not noted 587 * here occur there. 588 * 589 * ES2025 Intl draft rev 5ea95f8a98d660e94c177d6f5e88c6d2962123b1 590 */ 591 function InitializeNumberFormat(numberFormat, thisValue, locales, options) { 592 assert( 593 IsObject(numberFormat), 594 "InitializeNumberFormat called with non-object" 595 ); 596 assert( 597 intl_GuardToNumberFormat(numberFormat) !== null, 598 "InitializeNumberFormat called with non-NumberFormat" 599 ); 600 601 // Lazy NumberFormat data has the following structure: 602 // 603 // { 604 // requestedLocales: List of locales, 605 // style: "decimal" / "percent" / "currency" / "unit", 606 // 607 // // fields present only if style === "currency": 608 // currency: a well-formed currency code (IsWellFormedCurrencyCode), 609 // currencyDisplay: "code" / "symbol" / "narrowSymbol" / "name", 610 // currencySign: "standard" / "accounting", 611 // 612 // // fields present only if style === "unit": 613 // unit: a well-formed unit identifier (IsWellFormedUnitIdentifier), 614 // unitDisplay: "short" / "narrow" / "long", 615 // 616 // opt: // opt object computed in InitializeNumberFormat 617 // { 618 // localeMatcher: "lookup" / "best fit", 619 // 620 // nu: string matching a Unicode extension type, // optional 621 // } 622 // 623 // minimumIntegerDigits: integer ∈ [1, 21], 624 // 625 // // optional, mutually exclusive with the significant-digits option 626 // minimumFractionDigits: integer ∈ [0, 100], 627 // maximumFractionDigits: integer ∈ [0, 100], 628 // 629 // // optional, mutually exclusive with the fraction-digits option 630 // minimumSignificantDigits: integer ∈ [1, 21], 631 // maximumSignificantDigits: integer ∈ [1, 21], 632 // 633 // roundingPriority: "auto" / "lessPrecision" / "morePrecision", 634 // 635 // useGrouping: "auto" / "always" / "min2" / false, 636 // 637 // notation: "standard" / "scientific" / "engineering" / "compact", 638 // 639 // // optional, if notation is "compact" 640 // compactDisplay: "short" / "long", 641 // 642 // signDisplay: "auto" / "never" / "always" / "exceptZero" / "negative", 643 // 644 // trailingZeroDisplay: "auto" / "stripIfInteger", 645 // 646 // roundingIncrement: integer ∈ (1, 2, 5, 647 // 10, 20, 25, 50, 648 // 100, 200, 250, 500, 649 // 1000, 2000, 2500, 5000), 650 // 651 // roundingMode: "ceil" / "floor" / "expand" / "trunc" / 652 // "halfCeil" / "halfFloor" / "halfExpand" / "halfTrunc" / "halfEven", 653 // } 654 // 655 // Note that lazy data is only installed as a final step of initialization, 656 // so every NumberFormat lazy data object has *all* these properties, never a 657 // subset of them. 658 var lazyNumberFormatData = std_Object_create(null); 659 660 // Step 3. 661 var requestedLocales = CanonicalizeLocaleList(locales); 662 lazyNumberFormatData.requestedLocales = requestedLocales; 663 664 // Step 4. (Inlined call to CoerceOptionsToObject.) 665 // 666 // If we ever need more speed here at startup, we should try to detect the 667 // case where |options === undefined| and then directly use the default 668 // value for each option. For now, just keep it simple. 669 if (options === undefined) { 670 options = std_Object_create(null); 671 } else { 672 options = ToObject(options); 673 } 674 675 // Compute options that impact interpretation of locale. 676 677 // Step 5. 678 var opt = NEW_RECORD(); 679 lazyNumberFormatData.opt = opt; 680 681 // Steps 6-7. 682 var matcher = GetOption( 683 options, 684 "localeMatcher", 685 "string", 686 ["lookup", "best fit"], 687 "best fit" 688 ); 689 opt.localeMatcher = matcher; 690 691 // Step 8. 692 var numberingSystem = GetOption( 693 options, 694 "numberingSystem", 695 "string", 696 undefined, 697 undefined 698 ); 699 700 // Step 9. 701 if (numberingSystem !== undefined) { 702 numberingSystem = intl_ValidateAndCanonicalizeUnicodeExtensionType( 703 numberingSystem, 704 "numberingSystem", 705 "nu" 706 ); 707 } 708 709 // Step 10. 710 opt.nu = numberingSystem; 711 712 // Compute formatting options. 713 714 // Step 15. SetNumberFormatUnitOptions, steps 1-2. 715 var style = GetOption( 716 options, 717 "style", 718 "string", 719 ["decimal", "percent", "currency", "unit"], 720 "decimal" 721 ); 722 lazyNumberFormatData.style = style; 723 724 // Step 15. SetNumberFormatUnitOptions, step 3. 725 var currency = GetOption(options, "currency", "string", undefined, undefined); 726 727 // Step 15. SetNumberFormatUnitOptions, steps 4-5. 728 if (currency === undefined) { 729 if (style === "currency") { 730 ThrowTypeError(JSMSG_UNDEFINED_CURRENCY); 731 } 732 } else { 733 if (!IsWellFormedCurrencyCode(currency)) { 734 ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, currency); 735 } 736 } 737 738 // Step 15. SetNumberFormatUnitOptions, step 6. 739 var currencyDisplay = GetOption( 740 options, 741 "currencyDisplay", 742 "string", 743 ["code", "symbol", "narrowSymbol", "name"], 744 "symbol" 745 ); 746 747 // Step 15. SetNumberFormatUnitOptions, step 7. 748 var currencySign = GetOption( 749 options, 750 "currencySign", 751 "string", 752 ["standard", "accounting"], 753 "standard" 754 ); 755 756 // Step 15. SetNumberFormatUnitOptions, step 12. (Reordered) 757 if (style === "currency") { 758 // Step 15. SetNumberFormatUnitOptions, step 12.a. 759 currency = toASCIIUpperCase(currency); 760 lazyNumberFormatData.currency = currency; 761 762 // Step 15. SetNumberFormatUnitOptions, step 12.b. 763 lazyNumberFormatData.currencyDisplay = currencyDisplay; 764 765 // Step 15. SetNumberFormatUnitOptions, step 12.c. 766 lazyNumberFormatData.currencySign = currencySign; 767 } 768 769 // Step 15. SetNumberFormatUnitOptions, step 8. 770 var unit = GetOption(options, "unit", "string", undefined, undefined); 771 772 // Step 15. SetNumberFormatUnitOptions, steps 9-10. 773 if (unit === undefined) { 774 if (style === "unit") { 775 ThrowTypeError(JSMSG_UNDEFINED_UNIT); 776 } 777 } else { 778 if (!IsWellFormedUnitIdentifier(unit)) { 779 ThrowRangeError(JSMSG_INVALID_UNIT_IDENTIFIER, unit); 780 } 781 } 782 783 // Step 15. SetNumberFormatUnitOptions, step 11. 784 var unitDisplay = GetOption( 785 options, 786 "unitDisplay", 787 "string", 788 ["short", "narrow", "long"], 789 "short" 790 ); 791 792 // Step 15. SetNumberFormatUnitOptions, step 13. 793 if (style === "unit") { 794 lazyNumberFormatData.unit = unit; 795 lazyNumberFormatData.unitDisplay = unitDisplay; 796 } 797 798 // Step 16. (Not applicable in our implementation.) 799 800 // Steps 17-18. 801 var notation = GetOption( 802 options, 803 "notation", 804 "string", 805 ["standard", "scientific", "engineering", "compact"], 806 "standard" 807 ); 808 lazyNumberFormatData.notation = notation; 809 810 // Steps 19-20. 811 var mnfdDefault, mxfdDefault; 812 if (style === "currency" && notation === "standard") { 813 var cDigits = CurrencyDigits(currency); 814 mnfdDefault = cDigits; 815 mxfdDefault = cDigits; 816 } else { 817 mnfdDefault = 0; 818 mxfdDefault = style === "percent" ? 0 : 3; 819 } 820 821 // Step 21. 822 SetNumberFormatDigitOptions( 823 lazyNumberFormatData, 824 options, 825 mnfdDefault, 826 mxfdDefault, 827 notation 828 ); 829 830 // Steps 22 and 24.a. 831 var compactDisplay = GetOption( 832 options, 833 "compactDisplay", 834 "string", 835 ["short", "long"], 836 "short" 837 ); 838 if (notation === "compact") { 839 lazyNumberFormatData.compactDisplay = compactDisplay; 840 } 841 842 // Steps 23 and 24.b. 843 var defaultUseGrouping = notation !== "compact" ? "auto" : "min2"; 844 845 // Steps 25-26. 846 var useGrouping = GetStringOrBooleanOption( 847 options, 848 "useGrouping", 849 ["min2", "auto", "always", "true", "false"], 850 defaultUseGrouping 851 ); 852 853 // Steps 27-28. 854 if (useGrouping === "true" || useGrouping === "false") { 855 useGrouping = defaultUseGrouping; 856 } else if (useGrouping === true) { 857 useGrouping = "always"; 858 } 859 860 // Step 29. 861 assert( 862 useGrouping === "min2" || 863 useGrouping === "auto" || 864 useGrouping === "always" || 865 useGrouping === false, 866 `invalid 'useGrouping' value: ${useGrouping}` 867 ); 868 lazyNumberFormatData.useGrouping = useGrouping; 869 870 // Steps 30-31. 871 var signDisplay = GetOption( 872 options, 873 "signDisplay", 874 "string", 875 ["auto", "never", "always", "exceptZero", "negative"], 876 "auto" 877 ); 878 lazyNumberFormatData.signDisplay = signDisplay; 879 880 // We've done everything that must be done now: mark the lazy data as fully 881 // computed and install it. 882 initializeIntlObject(numberFormat, "NumberFormat", lazyNumberFormatData); 883 884 // Step 32. (Inlined call to ChainNumberFormat.) 885 if ( 886 numberFormat !== thisValue && 887 callFunction( 888 std_Object_isPrototypeOf, 889 GetBuiltinPrototype("NumberFormat"), 890 thisValue 891 ) 892 ) { 893 DefineDataProperty( 894 thisValue, 895 intlFallbackSymbol(), 896 numberFormat, 897 ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE 898 ); 899 900 return thisValue; 901 } 902 903 // Step 33. 904 return numberFormat; 905 } 906 /* eslint-enable complexity */ 907 908 /** 909 * 15.5.1 CurrencyDigits ( currency ) 910 * 911 * Returns the number of decimal digits to be used for the given currency. 912 * 913 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 914 */ 915 function CurrencyDigits(currency) { 916 assert(typeof currency === "string", "currency is a string value"); 917 assert(IsWellFormedCurrencyCode(currency), "currency is well-formed"); 918 assert(currency === toASCIIUpperCase(currency), "currency is all upper-case"); 919 920 // Step 1. 921 if (hasOwn(currency, currencyDigits)) { 922 return currencyDigits[currency]; 923 } 924 return 2; 925 } 926 927 function getNumberingSystems(locale) { 928 // ICU doesn't have an API to determine the set of numbering systems 929 // supported for a locale; it generally pretends that any numbering system 930 // can be used with any locale. Supporting a decimal numbering system 931 // (where only the digits are replaced) is easy, so we offer them all here. 932 // Algorithmic numbering systems are typically tied to one locale, so for 933 // lack of information we don't offer them. 934 // The one thing we can find out from ICU is the default numbering system 935 // for a locale. 936 var defaultNumberingSystem = intl_numberingSystem(locale); 937 return [defaultNumberingSystem, NUMBERING_SYSTEMS_WITH_SIMPLE_DIGIT_MAPPINGS]; 938 } 939 940 function numberFormatLocaleData() { 941 return { 942 nu: getNumberingSystems, 943 default: { 944 nu: intl_numberingSystem, 945 }, 946 }; 947 } 948 949 /** 950 * 15.5.2 Number Format Functions 951 * 952 * Create function to be cached and returned by Intl.NumberFormat.prototype.format. 953 * 954 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 955 */ 956 function createNumberFormatFormat(nf) { 957 // This function is not inlined in $Intl_NumberFormat_format_get to avoid 958 // creating a call-object on each call to $Intl_NumberFormat_format_get. 959 return function(value) { 960 // Step 1 (implicit). 961 962 // Step 2. 963 assert(IsObject(nf), "InitializeNumberFormat called with non-object"); 964 assert( 965 intl_GuardToNumberFormat(nf) !== null, 966 "InitializeNumberFormat called with non-NumberFormat" 967 ); 968 969 // Steps 3-5. 970 return intl_FormatNumber(nf, value, /* formatToParts = */ false); 971 }; 972 } 973 974 /** 975 * 15.3.3 get Intl.NumberFormat.prototype.format 976 * 977 * Returns a function bound to this NumberFormat that returns a String value 978 * representing the result of calling ToNumber(value) according to the 979 * effective locale and the formatting options of this NumberFormat. 980 * 981 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 982 */ 983 // Uncloned functions with `$` prefix are allocated as extended function 984 // to store the original name in `SetCanonicalName`. 985 function $Intl_NumberFormat_format_get() { 986 // Steps 1-3. 987 var thisArg = UnwrapNumberFormat(this); 988 var nf = thisArg; 989 if (!IsObject(nf) || (nf = intl_GuardToNumberFormat(nf)) === null) { 990 return callFunction( 991 intl_CallNumberFormatMethodIfWrapped, 992 thisArg, 993 "$Intl_NumberFormat_format_get" 994 ); 995 } 996 997 var internals = getNumberFormatInternals(nf); 998 999 // Step 4. 1000 if (internals.boundFormat === undefined) { 1001 // Steps 4.a-c. 1002 internals.boundFormat = createNumberFormatFormat(nf); 1003 } 1004 1005 // Step 5. 1006 return internals.boundFormat; 1007 } 1008 SetCanonicalName($Intl_NumberFormat_format_get, "get format"); 1009 1010 /** 1011 * 15.3.4 Intl.NumberFormat.prototype.formatToParts ( value ) 1012 * 1013 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 1014 */ 1015 function Intl_NumberFormat_formatToParts(value) { 1016 // Step 1. 1017 var nf = this; 1018 1019 // Step 2. 1020 if (!IsObject(nf) || (nf = intl_GuardToNumberFormat(nf)) === null) { 1021 return callFunction( 1022 intl_CallNumberFormatMethodIfWrapped, 1023 this, 1024 value, 1025 "Intl_NumberFormat_formatToParts" 1026 ); 1027 } 1028 1029 // Steps 3-4. 1030 return intl_FormatNumber(nf, value, /* formatToParts = */ true); 1031 } 1032 1033 /** 1034 * 15.3.5 Intl.NumberFormat.prototype.formatRange ( start, end ) 1035 * 1036 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 1037 */ 1038 function Intl_NumberFormat_formatRange(start, end) { 1039 // Step 1. 1040 var nf = this; 1041 1042 // Step 2. 1043 if (!IsObject(nf) || (nf = intl_GuardToNumberFormat(nf)) === null) { 1044 return callFunction( 1045 intl_CallNumberFormatMethodIfWrapped, 1046 this, 1047 start, 1048 end, 1049 "Intl_NumberFormat_formatRange" 1050 ); 1051 } 1052 1053 // Step 3. 1054 if (start === undefined || end === undefined) { 1055 ThrowTypeError( 1056 JSMSG_UNDEFINED_NUMBER, 1057 start === undefined ? "start" : "end", 1058 "NumberFormat", 1059 "formatRange" 1060 ); 1061 } 1062 1063 // Steps 4-6. 1064 return intl_FormatNumberRange(nf, start, end, /* formatToParts = */ false); 1065 } 1066 1067 /** 1068 * 15.3.6 Intl.NumberFormat.prototype.formatRangeToParts ( start, end ) 1069 * 1070 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6 1071 */ 1072 function Intl_NumberFormat_formatRangeToParts(start, end) { 1073 // Step 1. 1074 var nf = this; 1075 1076 // Step 2. 1077 if (!IsObject(nf) || (nf = intl_GuardToNumberFormat(nf)) === null) { 1078 return callFunction( 1079 intl_CallNumberFormatMethodIfWrapped, 1080 this, 1081 start, 1082 end, 1083 "Intl_NumberFormat_formatRangeToParts" 1084 ); 1085 } 1086 1087 // Step 3. 1088 if (start === undefined || end === undefined) { 1089 ThrowTypeError( 1090 JSMSG_UNDEFINED_NUMBER, 1091 start === undefined ? "start" : "end", 1092 "NumberFormat", 1093 "formatRangeToParts" 1094 ); 1095 } 1096 1097 // Steps 4-6. 1098 return intl_FormatNumberRange(nf, start, end, /* formatToParts = */ true); 1099 } 1100 1101 /** 1102 * 15.3.7 Intl.NumberFormat.prototype.resolvedOptions ( ) 1103 * 1104 * Returns the resolved options for a NumberFormat object. 1105 * 1106 * ES2024 Intl draft rev a1db4567870dbe505121a4255f1210338757190a 1107 */ 1108 function Intl_NumberFormat_resolvedOptions() { 1109 // Steps 1-3. 1110 var thisArg = UnwrapNumberFormat(this); 1111 var nf = thisArg; 1112 if (!IsObject(nf) || (nf = intl_GuardToNumberFormat(nf)) === null) { 1113 return callFunction( 1114 intl_CallNumberFormatMethodIfWrapped, 1115 thisArg, 1116 "Intl_NumberFormat_resolvedOptions" 1117 ); 1118 } 1119 1120 var internals = getNumberFormatInternals(nf); 1121 1122 // Steps 4-5. 1123 var result = { 1124 locale: internals.locale, 1125 numberingSystem: internals.numberingSystem, 1126 style: internals.style, 1127 }; 1128 1129 // currency, currencyDisplay, and currencySign are only present for currency 1130 // formatters. 1131 assert( 1132 hasOwn("currency", internals) === (internals.style === "currency"), 1133 "currency is present iff style is 'currency'" 1134 ); 1135 assert( 1136 hasOwn("currencyDisplay", internals) === (internals.style === "currency"), 1137 "currencyDisplay is present iff style is 'currency'" 1138 ); 1139 assert( 1140 hasOwn("currencySign", internals) === (internals.style === "currency"), 1141 "currencySign is present iff style is 'currency'" 1142 ); 1143 1144 if (hasOwn("currency", internals)) { 1145 DefineDataProperty(result, "currency", internals.currency); 1146 DefineDataProperty(result, "currencyDisplay", internals.currencyDisplay); 1147 DefineDataProperty(result, "currencySign", internals.currencySign); 1148 } 1149 1150 // unit and unitDisplay are only present for unit formatters. 1151 assert( 1152 hasOwn("unit", internals) === (internals.style === "unit"), 1153 "unit is present iff style is 'unit'" 1154 ); 1155 assert( 1156 hasOwn("unitDisplay", internals) === (internals.style === "unit"), 1157 "unitDisplay is present iff style is 'unit'" 1158 ); 1159 1160 if (hasOwn("unit", internals)) { 1161 DefineDataProperty(result, "unit", internals.unit); 1162 DefineDataProperty(result, "unitDisplay", internals.unitDisplay); 1163 } 1164 1165 DefineDataProperty( 1166 result, 1167 "minimumIntegerDigits", 1168 internals.minimumIntegerDigits 1169 ); 1170 1171 // Min/Max fraction digits are either both present or not present at all. 1172 assert( 1173 hasOwn("minimumFractionDigits", internals) === 1174 hasOwn("maximumFractionDigits", internals), 1175 "minimumFractionDigits is present iff maximumFractionDigits is present" 1176 ); 1177 1178 if (hasOwn("minimumFractionDigits", internals)) { 1179 DefineDataProperty( 1180 result, 1181 "minimumFractionDigits", 1182 internals.minimumFractionDigits 1183 ); 1184 DefineDataProperty( 1185 result, 1186 "maximumFractionDigits", 1187 internals.maximumFractionDigits 1188 ); 1189 } 1190 1191 // Min/Max significant digits are either both present or not present at all. 1192 assert( 1193 hasOwn("minimumSignificantDigits", internals) === 1194 hasOwn("maximumSignificantDigits", internals), 1195 "minimumSignificantDigits is present iff maximumSignificantDigits is present" 1196 ); 1197 1198 if (hasOwn("minimumSignificantDigits", internals)) { 1199 DefineDataProperty( 1200 result, 1201 "minimumSignificantDigits", 1202 internals.minimumSignificantDigits 1203 ); 1204 DefineDataProperty( 1205 result, 1206 "maximumSignificantDigits", 1207 internals.maximumSignificantDigits 1208 ); 1209 } 1210 1211 DefineDataProperty(result, "useGrouping", internals.useGrouping); 1212 1213 var notation = internals.notation; 1214 DefineDataProperty(result, "notation", notation); 1215 1216 // compactDisplay is only present when `notation` is "compact". 1217 if (notation === "compact") { 1218 DefineDataProperty(result, "compactDisplay", internals.compactDisplay); 1219 } 1220 1221 DefineDataProperty(result, "signDisplay", internals.signDisplay); 1222 DefineDataProperty(result, "roundingIncrement", internals.roundingIncrement); 1223 DefineDataProperty(result, "roundingMode", internals.roundingMode); 1224 DefineDataProperty(result, "roundingPriority", internals.roundingPriority); 1225 DefineDataProperty( 1226 result, 1227 "trailingZeroDisplay", 1228 internals.trailingZeroDisplay 1229 ); 1230 1231 // Step 6. 1232 return result; 1233 }