Stage 1 Draft / April 21, 2026

Proposal Intl Unit Protocol

1 NumberFormat Objects

1.1 Properties of the Intl.NumberFormat Prototype Object

1.1.1 Intl.NumberFormat.prototype.formatRange ( start, end )

When the formatRange method is called with arguments start and end, the following steps are taken:

  1. Let nf be the this value.
  2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
  3. If start is undefined or end is undefined, throw a TypeError exception.
  4. Let x be ? ToIntlMathematicalValueGetNumberFormatInput(nf, start).
  5. Let y be ? ToIntlMathematicalValueGetNumberFormatInput(nf, end).
  6. Return ? FormatNumericRange(nf, x, y).

1.1.2 Intl.NumberFormat.prototype.formatRangeToParts ( start, end )

When the formatRangeToParts method is called with arguments start and end, the following steps are taken:

  1. Let nf be the this value.
  2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
  3. If start is undefined or end is undefined, throw a TypeError exception.
  4. Let x be ? ToIntlMathematicalValueGetNumberFormatInput(nf, start).
  5. Let y be ? ToIntlMathematicalValueGetNumberFormatInput(nf, end).
  6. Return ? FormatNumericRangeToParts(nf, x, y).

1.1.3 Intl.NumberFormat.prototype.formatToParts ( value )

When the formatToParts method is called with an optional argument value, the following steps are taken:

  1. Let nf be the this value.
  2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
  3. Let x be ? ToIntlMathematicalValueGetNumberFormatInput(nf, value).
  4. Return FormatNumericToParts(nf, x).

1.2 Abstract Operations for NumberFormat Objects

1.2.1 Number Format Functions

A Number format function is an anonymous built-in function that has a [[NumberFormat]] internal slot.

When a Number format function F is called with optional argument value, the following steps are taken:

  1. Let nf be F.[[NumberFormat]].
  2. Assert: nf is an Object and nf has an [[InitializedNumberFormat]] internal slot.
  3. If value is not provided, let value be undefined.
  4. Let x be ? ToIntlMathematicalValueGetNumberFormatInput(nf, value).
  5. Return FormatNumeric(nf, x).

The "length" property of a Number format function is 1𝔽.

1.2.2 FormatNumeric ( numberFormat, x )

The abstract operation FormatNumeric takes arguments numberFormat (an Intl.NumberFormat) and x (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)) and returns a String. It performs the following steps when called:

  1. Let parts be PartitionNumberPattern(numberFormat, x).
  2. Let result be the empty String.
  3. For each Record { [[Type]], [[Value]] } part of parts, do
    1. Set result to the string-concatenation of result and part.[[Value]].
  4. Return result.

1.2.3 FormatNumericToParts ( numberFormat, x )

The abstract operation FormatNumericToParts takes arguments numberFormat (an Intl.NumberFormat) and x (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)) and returns an Array. It performs the following steps when called:

  1. Let parts be PartitionNumberPattern(numberFormat, x).
  2. Let result be ! ArrayCreate(0).
  3. Let n be 0.
  4. For each Record { [[Type]], [[Value]] } part of parts, do
    1. Let O be OrdinaryObjectCreate(%Object.prototype%).
    2. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
    3. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
    4. Perform ! CreateDataPropertyOrThrow(result, ! ToString(𝔽(n)), O).
    5. Increment n by 1.
  5. Return result.

1.2.4 PartitionNumberPattern ( numberFormat, x, inputRecord )

The abstract operation PartitionNumberPattern takes arguments numberFormat (an object initialized as a NumberFormat), x (an Intl mathematical value), and inputRecord (a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)) and returns a List of Records with fields [[Type]] (a String) and [[Value]] (a String). It creates the parts representing the mathematical value of x according to the effective locale and the formatting options of numberFormat. inputRecord.[[Unit]] must not be undefined if the style is "unit" or "currency", and it must be undefined otherwise. It performs the following steps when called:

  1. Let exponent be 0.
  2. Let x be inputRecord.[[Value]].
  3. Let unit be inputRecord.[[Unit]].
  4. If x is not-a-number, then
    1. Let n be an ILD String value indicating the NaN value.
  5. Else if x is positive-infinity, then
    1. Let n be an ILD String value indicating positive infinity.
  6. Else if x is negative-infinity, then
    1. Let n be an ILD String value indicating negative infinity.
  7. Else,
    1. If x is not negative-zero, then
      1. Assert: x is a mathematical value.
      2. If numberFormat.[[Style]] is "percent", set x be 100 × x.
      3. Set exponent to ComputeExponent(numberFormat, x).
      4. Set x to x × 10-exponent.
    2. Let formatNumberResult be FormatNumericToString(numberFormat, x).
    3. Let n be formatNumberResult.[[FormattedString]].
    4. Set x to formatNumberResult.[[RoundedNumber]].
  8. Let pattern be GetNumberFormatPattern(numberFormat, x , unit).
  9. Let result be a new empty List.
  10. Let patternParts be PartitionPattern(pattern).
  11. For each Record { [[Type]], [[Value]] } patternPart of patternParts, do
    1. Let p be patternPart.[[Type]].
    2. If p is "literal", then
      1. Append the Record { [[Type]]: "literal", [[Value]]: patternPart.[[Value]] } to result.
    3. Else if p is "number", then
      1. Let notationSubParts be PartitionNotationSubPattern(numberFormat, x, n, exponent).
      2. Set result to the list-concatenation of result and notationSubParts.
    4. Else if p is "plusSign", then
      1. Let plusSignSymbol be the ILND String representing the plus sign.
      2. Append the Record { [[Type]]: "plusSign", [[Value]]: plusSignSymbol } to result.
    5. Else if p is "minusSign", then
      1. Let minusSignSymbol be the ILND String representing the minus sign.
      2. Append the Record { [[Type]]: "minusSign", [[Value]]: minusSignSymbol } to result.
    6. Else if p is "percentSign" and numberFormat.[[Style]] is "percent", then
      1. Let percentSignSymbol be the ILND String representing the percent sign.
      2. Append the Record { [[Type]]: "percentSign", [[Value]]: percentSignSymbol } to result.
    7. Else if p is "unitPrefix" and numberFormat.[[Style]] is "unit", then
      1. Let unit be numberFormat.[[Unit]].
      2. Let unitDisplay be numberFormat.[[UnitDisplay]].
      3. Let mu be an ILD String value representing unit before x in unitDisplay form, which may depend on x in languages having different plural forms.
      4. Append the Record { [[Type]]: "unit", [[Value]]: mu } to result.
    8. Else if p is "unitSuffix" and numberFormat.[[Style]] is "unit", then
      1. Let unit be numberFormat.[[Unit]].
      2. Let unitDisplay be numberFormat.[[UnitDisplay]].
      3. Let mu be an ILD String value representing unit after x in unitDisplay form, which may depend on x in languages having different plural forms.
      4. Append the Record { [[Type]]: "unit", [[Value]]: mu } to result.
    9. Else if p is "currencyCode" and numberFormat.[[Style]] is "currency", then
      1. Let currency be numberFormat.[[Currency]] unit.
      2. Let cd be currency.
      3. Append the Record { [[Type]]: "currency", [[Value]]: cd } to result.
    10. Else if p is "currencyPrefix" and numberFormat.[[Style]] is "currency", then
      1. Let currency be numberFormat.[[Currency]] unit.
      2. Let currencyDisplay be numberFormat.[[CurrencyDisplay]].
      3. Let cd be an ILD String value representing currency before x in currencyDisplay form, which may depend on x in languages having different plural forms.
      4. Append the Record { [[Type]]: "currency", [[Value]]: cd } to result.
    11. Else if p is "currencySuffix" and numberFormat.[[Style]] is "currency", then
      1. Let currency be numberFormat.[[Currency]] unit.
      2. Let currencyDisplay be numberFormat.[[CurrencyDisplay]].
      3. Let cd be an ILD String value representing currency after x in currencyDisplay form, which may depend on x in languages having different plural forms. If the implementation does not have such a representation of currency, use currency itself.
      4. Append the Record { [[Type]]: "currency", [[Value]]: cd } to result.
    12. Else,
      1. Let unknown be an ILND String based on x and p.
      2. Append the Record { [[Type]]: "unknown", [[Value]]: unknown } to result.
  12. Return result.

1.2.5 GetNumberFormatPattern ( numberFormat, x, unit )

The abstract operation GetNumberFormatPattern takes arguments numberFormat (an Intl.NumberFormat), x (an Intl mathematical value), and unit (a String or undefined) and returns a String. It considers the resolved unit-related options in the number format object along with the final scaled and rounded number being formatted (an Intl mathematical value) and returns a pattern, a String value as described in 16.2.3. unit must not be undefined if the style is "unit" or "currency", and it must be undefined otherwise. It performs the following steps when called:

  1. Let resolvedLocaleData be numberFormat.[[LocaleData]].
  2. Let patterns be resolvedLocaleData.[[patterns]].
  3. Assert: patterns is a Record (see 16.2.3).
  4. Let style be numberFormat.[[Style]].
  5. If style is "percent", then
    1. Set patterns to patterns.[[percent]].
  6. Else if style is "unit", then
    1. Assert: unit is not undefined.
    2. Let unitDisplay be numberFormat.[[UnitDisplay]].
    3. Set patterns to patterns.[[unit]].
    4. If patterns doesn't have a field [[<unit>]], then
      1. Set unit to "fallback".
    5. Set patterns to patterns.[[<unit>]].
    6. Set patterns to patterns.[[<unitDisplay>]].
  7. Else if style is "currency", then
    1. Assert: unit is not undefined.
    2. Let currency be unit.
    3. Let currencyDisplay be numberFormat.[[CurrencyDisplay]].
    4. Let currencySign be numberFormat.[[CurrencySign]].
    5. Set patterns to patterns.[[currency]].
    6. If patterns doesn't have a field [[<currency>]], then
      1. Set currency to "fallback".
    7. Set patterns to patterns.[[<currency>]].
    8. Set patterns to patterns.[[<currencyDisplay>]].
    9. Set patterns to patterns.[[<currencySign>]].
  8. Else,
    1. Assert: style is "decimal".
    2. Assert: unit is undefined.
    3. Set patterns to patterns.[[decimal]].
  9. If x is negative-infinity, then
    1. Let category be negative-non-zero.
  10. Else if x is negative-zero, then
    1. Let category be negative-zero.
  11. Else if x is not-a-number, then
    1. Let category be positive-zero.
  12. Else if x is positive-infinity, then
    1. Let category be positive-non-zero.
  13. Else,
    1. Assert: x is a mathematical value.
    2. If x < 0, then
      1. Let category be negative-non-zero.
    3. Else if x > 0, then
      1. Let category be positive-non-zero.
    4. Else,
      1. Let category be positive-zero.
  14. Let signDisplay be numberFormat.[[SignDisplay]].
  15. If signDisplay is "never", then
    1. Let pattern be patterns.[[zeroPattern]].
  16. Else if signDisplay is "auto", then
    1. If category is positive-non-zero or positive-zero, then
      1. Let pattern be patterns.[[zeroPattern]].
    2. Else,
      1. Let pattern be patterns.[[negativePattern]].
  17. Else if signDisplay is "always", then
    1. If category is positive-non-zero or positive-zero, then
      1. Let pattern be patterns.[[positivePattern]].
    2. Else,
      1. Let pattern be patterns.[[negativePattern]].
  18. Else if signDisplay is "exceptZero", then
    1. If category is positive-zero or negative-zero, then
      1. Let pattern be patterns.[[zeroPattern]].
    2. Else if category is positive-non-zero, then
      1. Let pattern be patterns.[[positivePattern]].
    3. Else,
      1. Let pattern be patterns.[[negativePattern]].
  19. Else,
    1. Assert: signDisplay is "negative".
    2. If category is negative-non-zero, then
      1. Let pattern be patterns.[[negativePattern]].
    3. Else,
      1. Let pattern be patterns.[[zeroPattern]].
  20. Return pattern.

1.2.6 GetNumberFormatInput ( numberFormat, input )

The abstract operation GetNumberFormatInput takes arguments numberFormat (an Intl.NumberFormat) and input (an ECMAScript language value) and returns either a normal completion containing a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined), or a throw completion. It reads input as either a primitive or an object containing "value" and "unit" fields. It performs the following steps when called:

  1. If input is an Object, then
    1. Let value be ? Get(input, "value").
    2. Let inputUnit be ? GetOption(input, "unit", string, empty, undefined).
  2. Else,
    1. Let value be input.
    2. Let inputUnit be undefined.
  3. Let intlMV be ? ToIntlMathematicalValue(value).
  4. If numberFormat.[[Style]] is "unit", then
    1. Let formatterUnit be numberFormat.[[Unit]].
    2. If inputUnit is undefined and formatterUnit is undefined, then
      1. Throw a TypeError exception.
    3. Else if inputUnit is undefined, then
      1. Assert: IsWellFormedUnitIdentifier(formatterUnit) is true.
      2. Let resolvedUnit be formatterUnit.
    4. Else if formatterUnit is undefined, then
      1. If IsWellFormedUnitIdentifier(inputUnit) is false, throw a RangeError exception.
      2. Let resolvedUnit be inputUnit.
    5. Else,
      1. If SameValueNonNumber(formatterUnit, inputUnit) is false, throw a RangeError exception.
      2. Let resolvedUnit be inputUnit.
  5. Else if numberFormat.[[Style]] is "currency", then
    1. Let formatterUnit be numberFormat.[[Unit]].
    2. If inputUnit is undefined and formatterUnit is undefined, then
      1. Throw a TypeError exception.
    3. Else if inputUnit is undefined, then
      1. Assert: IsWellFormedCurrencyCode(formatterUnit) is true.
      2. Let resolvedUnit be formatterUnit.
    4. Else if formatterUnit is undefined, then
      1. If IsWellFormedCurrencyCode(inputUnit) is false, throw a RangeError exception.
      2. Let resolvedUnit be inputUnit.
    5. Else,
      1. If SameValueNonNumber(formatterUnit, inputUnit) is false, throw a RangeError exception.
      2. Let resolvedUnit be inputUnit.
    6. Set resolvedUnit to the ASCII-uppercase of resolvedUnit.
  6. Else,
    1. If inputUnit is not undefined, throw a TypeError exception.
  7. Return the Record { [[Value]]: intlMV, [[Unit]]: resolvedUnit }.

1.2.7 SetNumberFormatUnitOptions ( intlObj, options )

The abstract operation SetNumberFormatUnitOptions takes arguments intlObj (an Intl.NumberFormat) and options (an Object) and returns either a normal completion containing unused or a throw completion. It resolves the user-specified options relating to units onto intlObj. It performs the following steps when called:

  1. Let style be ? GetOption(options, "style", string, « "decimal", "percent", "currency", "unit" », "decimal").
  2. Set intlObj.[[Style]] to style.
  3. Let currency be ? GetOption(options, "currency", string, empty, undefined).
  4. If currency is undefined, then
    1. If style is "currency", throw a TypeError exception.
  5. Else,
    1. If IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
  6. If currency is not undefined and IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
  7. Let currencyDisplay be ? GetOption(options, "currencyDisplay", string, « "code", "symbol", "narrowSymbol", "name" », "symbol").
  8. Let currencySign be ? GetOption(options, "currencySign", string, « "standard", "accounting" », "standard").
  9. Let unit be ? GetOption(options, "unit", string, empty, undefined).
  10. If unit is undefined, then
    1. If style is "unit", throw a TypeError exception.
  11. Else,
    1. If IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
  12. If unit is not undefined and IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
  13. Let unitDisplay be ? GetOption(options, "unitDisplay", string, « "short", "narrow", "long" », "short").
  14. If style is "currency", then
    1. Set intlObj.[[Currency]] to the ASCII-uppercase of currency.
    2. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
    3. Set intlObj.[[CurrencySign]] to currencySign.
  15. If style is "unit", then
    1. Set intlObj.[[Unit]] to unit.
    2. Set intlObj.[[UnitDisplay]] to unitDisplay.
  16. Return unused.

1.2.8 PartitionNumberRangePattern ( numberFormat, x, y )

The abstract operation PartitionNumberRangePattern takes arguments numberFormat (an Intl.NumberFormat), x (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)), and y (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)) and returns either a normal completion containing a List of Records with fields [[Type]] (a String), [[Value]] (a String), and [[Source]] (a String), or a throw completion. It creates the parts for a localized number range according to x, y, and the formatting options of numberFormat. It performs the following steps when called:

  1. If x.[[Unit]] != y.[[Unit]], throw a RangeError exception.
  2. If x.[[Value]] is not-a-number or y.[[Value]] is not-a-number, throw a RangeError exception.
  3. Let xResult be PartitionNumberPattern(numberFormat, x).
  4. Let yResult be PartitionNumberPattern(numberFormat, y).
  5. If FormatNumeric(numberFormat, x) is FormatNumeric(numberFormat, y), then
    1. Let appxResult be FormatApproximately(numberFormat, xResult).
    2. For each element r of appxResult, do
      1. Set r.[[Source]] to "shared".
    3. Return appxResult.
  6. Let result be a new empty List.
  7. For each element r of xResult, do
    1. Append the Record { [[Type]]: r.[[Type]], [[Value]]: r.[[Value]], [[Source]]: "startRange" } to result.
  8. Let rangeSeparator be an ILND String value used to separate two numbers.
  9. Append the Record { [[Type]]: "literal", [[Value]]: rangeSeparator, [[Source]]: "shared" } to result.
  10. For each element r of yResult, do
    1. Append the Record { [[Type]]: r.[[Type]], [[Value]]: r.[[Value]], [[Source]]: "endRange" } to result.
  11. Return CollapseNumberRange(numberFormat, result).

1.2.9 FormatNumericRange ( numberFormat, x, y )

The abstract operation FormatNumericRange takes arguments numberFormat (an Intl.NumberFormat), x (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)), and y (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)) and returns either a normal completion containing a String or a throw completion. It performs the following steps when called:

  1. Let parts be ? PartitionNumberRangePattern(numberFormat, x, y).
  2. Let result be the empty String.
  3. For each element part of parts, do
    1. Set result to the string-concatenation of result and part.[[Value]].
  4. Return result.

1.2.10 FormatNumericRangeToParts ( numberFormat, x, y )

The abstract operation FormatNumericRangeToParts takes arguments numberFormat (an Intl.NumberFormat), x (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)), and y (an Intl mathematical value a Record with fields [[Value]] (an Intl mathematical value) and [[Unit]] (a String or undefined)) and returns either a normal completion containing an Array or a throw completion. It performs the following steps when called:

  1. Let parts be ? PartitionNumberRangePattern(numberFormat, x, y).
  2. Let result be ! ArrayCreate(0).
  3. Let n be 0.
  4. For each element part of parts, do
    1. Let O be OrdinaryObjectCreate(%Object.prototype%).
    2. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
    3. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
    4. Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]).
    5. Perform ! CreateDataPropertyOrThrow(result, ! ToString(𝔽(n)), O).
    6. Increment n by 1.
  5. Return result.

Copyright & Software License

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.