Skip to content
132 changes: 131 additions & 1 deletion intl.emu
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,134 @@
</p>
</emu-note>

<emu-clause id="sup-amount-abstract-operations">
<h1>Abstract Operations of the Amount Object</h1>

<emu-clause id="sec-amount-unit-category-data">
<h1>Unit Category Data</h1>
<p>
Unit category data is derived from CLDR file
<a href="https://github.com/unicode-org/cldr/blob/main/common/validity/unit.xml"><code>validity/unit.xml</code></a>.
As described in <a href="https://unicode.org/reports/tr35/tr35.html#Validity_Data">Unicode Technical Standard #35 Part 1, Validity Data</a>,
Comment thread
eemeli marked this conversation as resolved.
Outdated
the body of the <code>&lt;id type='unit' idStatus='regular'></code> element contains
the supported units, represented as a whitespace-delimited list of sequences of subtags.
Comment thread
eemeli marked this conversation as resolved.
Outdated
For each unit, the first subtag provides the unit's category, and the remaining subtags are the unit identifier.
Comment thread
eemeli marked this conversation as resolved.
Outdated
</p>
</emu-clause>

<emu-clause id="sec-amount-unit-preference-data">
<h1>Unit Preference Data</h1>
<p>Unit preference data is derived from CLDR file <a href="https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml"><code>supplemental/units.xml</code></a>. As described in <a href="https://unicode.org/reports/tr35/tr35-info.html#Unit_Preferences_Data">Unicode Technical Standard #35 Part 6 Supplemental, Unit Preferences Data</a>, each <code>&lt;unitPreferences&gt;</code> element contains the preferred units for its <code>category</code> and <code>usage</code>. A <code>&lt;unitPreferences&gt;</code> element contains one or more <code>&lt;unitPreference&gt;</code> elements, which define the <code>regions</code> (a space-delimited list of region identifiers) and optionally the <code>geq</code> threshold for the unit preference.</p>
Comment thread
eemeli marked this conversation as resolved.
Outdated
</emu-clause>

<emu-clause id="sup-amount-getamountconverttounit" type="abstract operation">
<h1>
GetAmountConvertToUnit (
_sourceValue_: a Number,
_sourceUnit_: a String,
_options_: an Object,
): either a normal completion containing a String or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the appropriate unit identifier to use as the conversion target.</dd>
<dt>redefinition</dt>
<dd>true</dd>
</dl>
<p>This definition supersedes the definition provided in <emu-xref href="#sec-amount-getamountconverttounit"></emu-xref>.</p>
<emu-alg>
1. Let _unit_ be ? GetOption(_options_, *"unit"*, ~string~, ~empty~, *undefined*).
1. Let _usage_ be ? GetOption(_options_, *"usage"*, ~string~, ~empty~, *undefined*).
1. Let _locale_ be ? Get(_options_, *"locale"*).
1. If _unit_ is a String, then
1. If _usage_ is not *undefined*, throw a *TypeError* exception.
1. If _locale_ is not *undefined*, throw a *TypeError* exception.
1. If _unit_ is the empty String, throw a *RangeError* exception.
1. Return _unit_.
Comment thread
eemeli marked this conversation as resolved.
Outdated
1. If _usage_ is *undefined*, then
1. If _locale_ is *undefined*, throw a *TypeError* exception.
1. Set _usage_ to *"default"*.
1. Let _category_ be ? GetUnitCategory(_sourceUnit_).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this depends upon the convertTo receiver rather than invocation arguments, I think it should be performed before interacting with options at all. But again, such a fix can be deferred beyond this PR.

1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locale_).
1. If _requestedLocales_ is an empty List, let _resolvedLocale_ be DefaultLocale(); else let _resolvedLocale_ be the first element of _requestedLocales_.
1. Let _maximalLocale_ be the result of the <a href="https://unicode.org/reports/tr35/#Likely_Subtags">Add Likely Subtags</a> algorithm applied to _resolvedLocale_. If an error is signaled, set _maximalLocale_ to _resolvedLocale_.
1. Let _region_ be GetLocaleRegion(_maximalLocale_).
1. If _region_ is *undefined*, set _region_ to *"001"*.
1. Let _preferredUnits_ be ? GetPreferredUnits(_category_, _usage_, _region_).
1. Repeat, while _preferredUnits_ has more than one element,
1. Let _entry_ be the first element of _preferredUnits_.
1. Set _unit_ to _entry_.[[Unit]].
1. Let _threshold_ be _entry_.[[Threshold]].
1. Let _convertedValue_ be ? ConvertUnitValue(_sourceValue_, _sourceUnit_, _unit_).
1. If _convertedValue_ is not finite or _convertedValue_ ≥ _threshold_, return _unit_.
Comment thread
eemeli marked this conversation as resolved.
Outdated
1. Remove the first element of _preferredUnits_.
1. Assert: _preferredUnits_ has exactly one element.
1. Let _entry_ be the first element of _preferredUnits_.
1. Set _unit_ to _entry_.[[Unit]].
1. Return _unit_.
Comment on lines +66 to +75
Copy link
Copy Markdown
Member

@gibson042 gibson042 May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These steps can be greatly simplified.

Suggested change
1. Let _entry_ be the first element of _preferredUnits_.
1. Let _unit_ be _entry_.[[Unit]].
1. Repeat, while _preferredUnits_ has more than one element,
1. Let _threshold_ be _entry_.[[Threshold]].
1. Let _convertedValue_ be ? ConvertUnitValue(_sourceValue_, _sourceUnit_, _unit_).
1. If _convertedValue_ is not finite or abs(ℝ(_convertedValue_)) ≥ _threshold_, return _unit_.
1. Remove the first element of _preferredUnits_.
1. Set _entry_ to the first element of _preferredUnits_.
1. Set _unit_ to _entry_.[[Unit]].
1. Return _unit_.
1. Let _unit_ be ~unset~.
1. For each element _entry_ of _preferredUnits_, do
1. Set _unit_ to _entry_.[[Unit]].
1. Let _convertedValue_ be ? ConvertUnitValue(_sourceValue_, _sourceUnit_, _unit_).
1. If _convertedValue_ is not finite or abs(ℝ(_convertedValue_)) ≥ _entry_.[[Threshold]], return _unit_.
1. Assert: _unit_ is a String.
1. Return _unit_.

</emu-alg>
</emu-clause>

<emu-clause id="sec-amount-getunitcategory" type="abstract operation">
<h1>
GetUnitCategory (
_unit_: a String,
): either a normal completion containing a String or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the CLDR unit category for _unit_ using the <emu-xref href="#sec-amount-unit-category-data">unit category data</emu-xref>.</dd>
</dl>
<emu-alg>
1. Let _unitTags_ be a List of Strings constructed by splitting by white space the body of the <code>&lt;id type='unit' idStatus='regular'></code> element of the <emu-xref href="#sec-amount-unit-category-data">unit category data</emu-xref> and discarding any empty elements.
1. For each element _tag_ of _unitTags_, do
1. Let _i_ be StringIndexOf(_tag_, *"-"*, 0).
1. Assert: _i_ is not ~not-found~ and _i_ > 0.
1. Let _tagUnit_ be the substring of _tag_ from _i_ + 1.
1. If SameValue(_unit_, _tagUnit_) is *true*, then
1. Let _category_ be the substring of _tag_ from 0 to _i_.
1. Return _category_.
1. Throw a *RangeError* exception.
</emu-alg>
</emu-clause>

<emu-clause id="sec-amount-getpreferredunits" type="abstract operation">
<h1>
GetPreferredUnits (
_category_: a String,
_usage_: a String,
_region_: a String,
): either a normal completion containing a List of Records or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the preferred units to use for the given category, usage, and region.</dd>
Comment thread
eemeli marked this conversation as resolved.
Outdated
</dl>
<emu-alg>
1. If _category_ is the <code>category</code> and _usage_ is the <code>usage</code> of a <code>&lt;unitPreferences&gt;</code> element in the <emu-xref href="#sec-amount-unit-preference-data">unit preference data</emu-xref>, then
1. Let _unitPreferencesParent_ be that element.
1. Else,
1. Throw a *RangeError* exception.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These steps seem to be missing some important functionality from Compute the preferred output unit, e.g. "land-agriculture-grain ⊂ land-agriculture ⊂ land ⊂ default" and "CH (Switzerland) ⊂ 155 (Western Europe) ⊂ 150 (Europe) ⊂ 001 (World)".

Copy link
Copy Markdown
Member Author

@eemeli eemeli May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd missed that, thanks. I'll add the category usage fallback.

On region fallback, I'd prefer to follow the LDML note

This loop can be optimized to only include containing regions that occur in the data (eg, only 001 in LDML 45).

as the unit data continues not to include any numeric codes other than 001. With that in mind, I'd prefer not introducing this chain of region fallbacks unless that actually becomes necessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's necessary to match the algorithm defined in UTS #35 now, otherwise if such data does get introduced then implementations using ICU will become nonconformant upon upgrade and implementations not using ICU will provide inferior user experience. We want to align with that upstream standard.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage fallbacking is added in 64a9f69.

I'll add a TODO about the region containment fallbacking.

1. Assert: _region_ can be matched by the <code>unicode_region_id</code> Unicode locale nonterminal.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should be covered by a more specific type for the parameter.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like this?

region: a String matching the unicode_region_id Unicode locale nonterminal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I see now that even GetLocaleRegion is documented to return just a String, so I'm comfortable with deferring this to a later cleanup.

1. Set _region_ to the ASCII-uppercase of _region_.
1. Let _fallback_ be an empty List.
1. Let _match_ be an empty List.
1. Let _unitPreferences_ be the List of <code>&lt;unitPreference&gt;</code> elements in _unitPreferencesParent_.
1. For each element _unitPreference_ of _unitPreferences_, do
1. Assert: _unitPreference_ has an attribute <code>regions</code>.
1. Let _regionsAttr_ be the String value of the <code>regions</code> attribute of _unitPreference_.
1. Let _regions_ be StringSplitToList(_regionsAttr_, *" "*).
1. Let _geq_ be the mathematical value of the <code>geq</code> attribute of _unitPreference_ if present, or 1 otherwise.
1. Let _unit_ be the String value of the body of the _unitPreference_ element.
1. If _regions_ contains _region_, append the Record { [[Unit]]: _unit_, [[Threshold]]: _geq_ } to _match_.
1. Else if _regions_ contains *"001"*, append the Record { [[Unit]]: _unit_, [[Threshold]]: _geq_ } to _fallback_.
1. If _match_ is not empty, return _match_.
1. Assert: _fallback_ is not empty.
1. Return _fallback_.
</emu-alg>
Comment thread
eemeli marked this conversation as resolved.
</emu-clause>
Comment on lines +102 to +177
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just looking ahead, I think we want a single operation to correspond with Compute the preferred output unit.

Suggested change
<emu-clause id="sec-amount-getpreferredunits" type="abstract operation">
<h1>
GetPreferredUnits (
_category_: a String,
_usage_: a String,
_region_: a String,
): either a normal completion containing a List of Records or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the preferred units to use for the given category, usage, and region, and returns them ordered by descending threshold.</dd>
</dl>
<emu-alg>
1. Let _unitPreferenceElements_ be ? GetPreferredUnitElements(_category_, _usage_).
1. Assert: _region_ can be matched by the <code>unicode_region_id</code> Unicode locale nonterminal.
1. Set _region_ to the ASCII-uppercase of _region_.
1. Let _fallback_ be an empty List.
1. Let _match_ be an empty List.
1. For each element _unitPreference_ of _unitPreferenceElements_, do
1. Assert: _unitPreference_ has an attribute <code>regions</code>.
1. Let _regionsAttr_ be the String value of the <code>regions</code> attribute of _unitPreference_.
1. Let _regions_ be StringSplitToList(_regionsAttr_, *" "*).
1. Let _geq_ be the mathematical value of the <code>geq</code> attribute of _unitPreference_ if present, or 1 otherwise.
1. Let _unit_ be the String value of the body of the _unitPreference_ element.
1. If _regions_ contains _region_, append the Record { [[Unit]]: _unit_, [[Threshold]]: _geq_ } to _match_.
1. Else if _regions_ contains *"001"*, append the Record { [[Unit]]: _unit_, [[Threshold]]: _geq_ } to _fallback_.
1. If _match_ is not empty, return _match_.
1. Assert: _fallback_ is not empty.
1. Return _fallback_.
</emu-alg>
<emu-note>
<p><a href="https://unicode.org/reports/tr35/tr35-info.html#constraints">Unicode Technical Standard #35 Part 1, Unit Preferences Constraints</a> specifies that the <emu-xref href="#sec-amount-unit-preference-data">unit preference data</emu-xref> for a given category, usage, and region-set is already in descending order [by threshold].</p>
</emu-note>
</emu-clause>
<emu-clause id="sec-amount-getpreferredunitelements" type="abstract operation">
<h1>
GetPreferredUnitElements (
_category_: a String,
_usage_: a String,
): either a normal completion containing a List or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the <code>&lt;unitPreference&gt;</code> elements in the <emu-xref href="#sec-amount-unit-preference-data">unit preference data</emu-xref> that match the given category and usage.</dd>
</dl>
<emu-alg>
1. Repeat,
1. If _category_ is the <code>category</code> and _usage_ is the <code>usage</code> of a <code>&lt;unitPreferences&gt;</code> element in the <emu-xref href="#sec-amount-unit-preference-data">unit preference data</emu-xref>, then
1. Let _unitPreferencesParent_ be that element.
1. Let _unitPreferenceElements_ be the List of <code>&lt;unitPreference&gt;</code> elements in _unitPreferencesParent_.
1. Return _unitPreferenceElements_.
1. Else if _usage_ is not *"default"*, then
1. Let _len_ be the length of _usage_.
1. Let _idx_ be StringLastIndexOf(_usage_, *"-"*, _len_ - 1).
1. If _idx_ is ~not-found~ or _idx_ = 0, set _usage_ to *"default"*; else set _usage_ to the substring of _usage_ from 0 to _idx_.
1. Else,
1. Throw a *RangeError* exception.
</emu-alg>
<emu-note>
<p>
This abstract operation implements the first two steps of the
<a href="https://unicode.org/reports/tr35/tr35-info.html#compute-the-preferred-output-unit">Compute the preferred output unit</a>
algorithm specified in
<a href="https://unicode.org/reports/tr35/tr35-info.html#Unit_Preferences">Unicode Technical Standard #35 Part 6, Supplemental, Unit Preferences</a>.
</p>
</emu-note>
<emu-note type="editor">
<p>
TODO: The UTS #35 algorithm includes fallbacking for regions based on
<a href="https://www.unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html">UN M.49 territory containment</a>,
but the unit preference data does not (yet) include any other numeric regions than 001.
We'll probably need to implement that in order to be compatible with possible future CLDR updates.
</p>
</emu-note>
</emu-clause>
<emu-clause id="sec-amount-getpreferredunits" type="abstract operation">
<h1>
GetPreferredUnits (
_category_: a String,
_usage_: a String,
_region_: a String,
): either a normal completion containing a List of Records or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the preferred units to use for the given category, usage, and region according to the <a href="https://unicode.org/reports/tr35/tr35-info.html#compute-the-preferred-output-unit">Compute the preferred output unit</a> algorithm specified in <a href="https://unicode.org/reports/tr35/tr35-info.html#Unit_Preferences">Unicode Technical Standard #35 Part 6 Supplemental, Unit Preferences</a>, and returns them ordered by descending threshold.</dd>
</dl>
<emu-alg>
1. Assert: _region_ can be matched by the <code>unicode_region_id</code> Unicode locale nonterminal and the ASCII-uppercase of _region_ is _region_.
1. Let _candidateElements_ be ~unset~.
1. Repeat, while _candidateElements_ is ~unset~,
1. If _category_ is the <code>category</code> and _usage_ is the <code>usage</code> of a <code>&lt;unitPreferences&gt;</code> element in the <emu-xref href="#sec-amount-unit-preference-data">unit preference data</emu-xref>, then
1. Let _unitPreferencesElement_ be that element.
1. Set _candidateElements_ to the List of <code>&lt;unitPreference&gt;</code> elements in _unitPreferencesElement_.
1. Else,
1. Assert: _usage_ is not *"default"*.
1. Let _len_ be the length of _usage_.
1. Let _idx_ be StringLastIndexOf(_usage_, *"-"*, _len_ - 1).
1. If _idx_ is ~not-found~ or _idx_ = 0, set _usage_ to *"default"*; else set _usage_ to the substring of _usage_ from 0 to _idx_.
1. Let _matchingUnits_ be a new empty List.
1. Repeat, while _matchingElements_ is empty,
1. For each element _unitPreference_ of _candidateElements_, do
1. Assert: _unitPreference_ has an attribute <code>regions</code>.
1. Let _regionsAttr_ be the String value of the <code>regions</code> attribute of _unitPreference_.
1. Let _regions_ be StringSplitToList(_regionsAttr_, *" "*).
1. Let _geq_ be the mathematical value of the <code>geq</code> attribute of _unitPreference_ if present, or 1 otherwise.
1. Let _unit_ be the String value of the body of the _unitPreference_.
1. If _regions_ contains _region_, append the Record { [[Unit]]: _unit_, [[Threshold]]: _geq_ } to _matchingUnits_.
1. If _matchingElements_ is empty, then
1. Assert: _region_ is not *"001"*.
1. Set _region_ to *"001".
1. Return _matchingUnits_.
</emu-alg>
<emu-note>
<p><a href="https://unicode.org/reports/tr35/tr35-info.html#constraints">Unicode Technical Standard #35 Part 1, Unit Preferences Constraints</a> specifies that the <emu-xref href="#sec-amount-unit-preference-data">unit preference data</emu-xref> for a given _category_, _usage_, and _region_ is non-empty and already in descending order [by threshold].</p>
</emu-note>
<emu-note type="editor">
<p>
TODO: The UTS #35 algorithm includes region fallback based on
<a href="https://www.unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html">UN M.49 territory containment</a>,
but this algorithm currently only falls back to region <code>001</code>.
The unit preference data does not (yet) include any other numeric regions, but we'll probably need revision in order to be compatible with possible future CLDR updates.
</p>
</emu-note>
</emu-clause>

</emu-clause>

<emu-clause id="sup-properties-of-the-amount-prototype-object">
<h1>Properties of the Amount Prototype Object</h1>

Expand Down Expand Up @@ -67,8 +195,10 @@

<emu-clause id="numberformat-objects">
<h1>NumberFormat Objects</h1>

<emu-clause id="sec-numberformat-abstracts">
<h1>Abstract Operations for NumberFormat Objects</h1>

<emu-clause id="sec-getnumberformatpattern" type="abstract operation">
<h1>
GetNumberFormatPattern (
Expand Down Expand Up @@ -202,7 +332,7 @@
1. Set _intlObj_.[[CurrencyDisplay]] to _currencyDisplay_.
1. Set _intlObj_.[[CurrencySign]] to _currencySign_.
1. If _style_ is *"unit"*, then
1. <ins>If _unit_ is not *undefined*, </ins> <del>Set</del> <ins>set</ins> _intlObj_.[[Unit]] to _unit_.
1. <ins>If _unit_ is not *undefined*,</ins> <del>Set</del> <ins>set</ins> _intlObj_.[[Unit]] to _unit_.
1. Set _intlObj_.[[UnitDisplay]] to _unitDisplay_.
1. Return ~unused~.
</emu-alg>
Expand Down
60 changes: 43 additions & 17 deletions spec.emu
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ location: https://github.com/tc39/proposal-amount/

<emu-clause id="sec-amount-getamountoptions" type="abstract operation">
<h1>GetAmountOptions (
_opts_: an Object
_options_: an ECMAScript language value,
): either a normal completion containing a Record with fields [[FractionDigits]] (a non-negative integer or *undefined*), [[RoundingMode]] (a <emu-xref href="#dfn-amount-rounding-mode">rounding mode</emu-xref>), [[SignificantDigits]] (a positive integer or *undefined*), and [[Unit]] (a String or *undefined*) or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It validates the given _options_ (an ECMAScript object) for creating an Amount and returns a Record with slots set to appropriate marthematical values (or *undefined*).</dd>
<dd>It validates the given _options_ for creating an Amount and returns a Record with slots set to appropriate marthematical values (or *undefined*).</dd>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This operation is no longer unique to Amount creation.

Suggested change
<dd>It validates the given _options_ for creating an Amount and returns a Record with slots set to appropriate marthematical values (or *undefined*).</dd>
<dd>It validates the given _options_ and returns their resolved values.</dd>

</dl>
<emu-alg>
1. Let _opts_ be ? GetOptionsObject(_opts_).
1. Let _opts_ be ? GetOptionsObject(_options_).
1. Let _fractionDigits_ be ? GetOption(_opts_, *"fractionDigits"*, ~number~, ~empty~, *undefined*).
1. Let _roundingMode_ be ? GetOption(_opts_, *"roundingMode"*, ~string~, « *"ceil"*, *"floor"*, *"expand"*, *"trunc"*, *"halfCeil"*, *"halfFloor"*, *"halfExpand"*, *"halfTrunc"*, *"halfEven"* », *"halfEven"*).
1. Let _significantDigits_ be ? GetOption(_opts_, *"significantDigits"*, ~number~, ~empty~, *undefined*).
Expand All @@ -105,19 +105,21 @@ location: https://github.com/tc39/proposal-amount/

<emu-clause id="sec-amount-getamountconverttooptions" type="abstract operation">
<h1>GetAmountConvertToOptions (
_opts_: an Object
_sourceValue_: a Number,
_sourceUnit_: a String,
_options_: an ECMAScript language value,
): either a normal completion containing a Record with fields [[FractionDigits]] (a non-negative integer or *undefined*), [[RoundingMode]] (a <emu-xref href="#dfn-amount-rounding-mode">rounding mode</emu-xref>), [[SignificantDigits]] (a positive integer or *undefined*), and [[Unit]] (a String or *undefined*) or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It validates the given _options_ (an ECMAScript object) for converting an Amount to another Amount and returns a Record with slots set to appropriate marthematical values (or *undefined*).</dd>
<dd>It validates the given _options_ for converting an Amount to another Amount and returns a Record with slots set to appropriate marthematical values (or *undefined*).</dd>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<dd>It validates the given _options_ for converting an Amount to another Amount and returns a Record with slots set to appropriate marthematical values (or *undefined*).</dd>
<dd>It validates the given _options_ for translating an Amount's value to a new unit and returns their resolved values.</dd>

</dl>
<emu-alg>
1. Let _opts_ be ? GetOptionsObject(_opts_).
1. Let _opts_ be ? GetOptionsObject(_options_).
1. Let _fractionDigits_ be ? GetOption(_opts_, *"fractionDigits"*, ~number~, ~empty~, *undefined*).
1. Let _roundingMode_ be ? GetOption(_opts_, *"roundingMode"*, ~string~, « *"ceil"*, *"floor"*, *"expand"*, *"trunc"*, *"halfCeil"*, *"halfFloor"*, *"halfExpand"*, *"halfTrunc"*, *"halfEven"* », *"halfEven"*).
1. Let _significantDigits_ be ? GetOption(_opts_, *"significantDigits"*, ~number~, ~empty~, *undefined*).
1. Let _unit_ be ? GetOption(_opts_, *"unit"*, ~string~, ~empty~, *undefined*).
1. Let _unit_ be ? GetAmountConvertToUnit(_sourceValue_, _sourceUnit_, _opts_).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes amount.convertTo({ fractionDigits: 1.5, unit: "" }) throw because of unit while new Amount(0, { fractionDigits: 1.5, unit: "" }) throws because of fractionDigits. I think the new behavior is better (#92), but more importantly, the algorithms should be kept in sync. Ideally, they just get combined into a single operation for Amount to call like ? GetAmountOptions(_opts_) and Amount.prototype.convertTo to call like ? GetAmountOptions(_opts_, _sourceValue_, _sourceUnit_), with steps like

           1. Let _significantDigits_ be ? GetOption(_opts_, *"significantDigits"*, ~number~, ~empty~, *undefined*).
-          1. Let _unit_ be ? GetAmountConvertToUnit(_sourceValue_, _sourceUnit_, _opts_).
+          1. Let _unit_ be ? GetOption(_opts_, *"unit"*, ~string~, ~empty~, *undefined*).
+          1. If _unit_ is the empty String, throw a *RangeError* exception.
+          1. If _sourceValue_ is present or _sourceUnit_ is present, then
+            1. Assert: _sourceValue_ is present and _sourceUnit_ is present.
+            1. Set _unit_ to ? SelectUnitConversionTarget(_sourceValue_, _sourceUnit_, _unit_, _opts_).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In b853e19 I made it so that the handling of the options shared between the constructor and unit conversion is always done via GetAmountOptions.

1. If _fractionDigits_ is not *undefined*, then
1. If _significantDigits_ is not *undefined*, throw a *RangeError* exception.
1. If _fractionDigits_ is not an integral Number, throw a *RangeError* exception.
Expand All @@ -127,11 +129,36 @@ location: https://github.com/tc39/proposal-amount/
1. If _significantDigits_ is not an integral Number, throw a *RangeError* exception.
1. Set _significantDigits_ to ℝ(_significantDigits_).
1. If _significantDigits_ is not in the inclusive interval from 1 to 21, throw a *RangeError* exception.
1. If _unit_ is the empty String, throw a *RangeError* exception.
1. Return the Record { [[FractionDigits]]: _fractionDigits_, [[RoundingMode]]: _roundingMode_, [[SignificantDigits]]: _significantDigits_, [[Unit]]: _unit_ }.
</emu-alg>
</emu-clause>

<emu-clause id="sec-amount-getamountconverttounit" type="abstract operation">
<h1>
GetAmountConvertToUnit (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: rename (both here and in intl.emu).

Suggested change
GetAmountConvertToUnit (
SelectTargetUnit (

_sourceValue_: a Number,
_sourceUnit_: a String,
_options_: an Object,
): either a normal completion containing a String or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It determines the appropriate unit identifier to use as the conversion target.</dd>
</dl>
<p>
An ECMAScript implementation that includes the ECMA-402 Internationalization API
must implement this method as specified in the ECMA-402 specification.
If an ECMAScript implementation does not include the ECMA-402 API,
GetAmountConvertToUnit performs the following steps when called:
</p>
<emu-alg>
1. Let _unit_ be ? GetOption(_options_, *"unit"*, ~string~, ~empty~, *undefined*).
1. If _unit_ is the empty String, throw a *RangeError* exception.
1. If _unit_ is *undefined*, throw a *TypeError* exception.
1. Return _unit_.
</emu-alg>
</emu-clause>

<emu-clause id="sec-amount-createformatterobject" type="abstract operation">
<h1>CreateFormatterObject (
_roundingMode_: a String,
Expand Down Expand Up @@ -182,7 +209,7 @@ location: https://github.com/tc39/proposal-amount/

<emu-clause id="sec-amount-unit-conversion-data">
<h1>Unit Conversion Data</h1>
<p>Unit conversion data is derived from CLDR file <a href="https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml"><code>units.xml</code></a>. As described in <a href="https://unicode.org/reports/tr35/tr35-info.html#conversion-data">Unicode Technical Standard #35 Part 6 Supplemental, Conversion Data</a>, each <code>&lt;convertUnit&gt;</code> element defines how to convert a <code>source</code> unit into a compatible <code>baseUnit</code>. An ECMAScript implementation must ignore all <code>special</code> conversions and support all conversions based on <code>factor</code> and/or <code>offset</code>, interpreting the value for each as an arithmetic expression with mathematical value operands (noting the respective defaults of 1 and 0 and the implicit presence of an identity mapping for each unit identified as the value of a <code>baseUnit</code>).</p>
<p>Unit conversion data is derived from CLDR file <a href="https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml"><code>supplemental/units.xml</code></a>. As described in <a href="https://unicode.org/reports/tr35/tr35-info.html#conversion-data">Unicode Technical Standard #35 Part 6 Supplemental, Conversion Data</a>, each <code>&lt;convertUnit&gt;</code> element defines how to convert a <code>source</code> unit into a compatible <code>baseUnit</code>. An ECMAScript implementation must ignore all <code>special</code> conversions and support all conversions based on <code>factor</code> and/or <code>offset</code>, interpreting the value for each as an arithmetic expression with mathematical value operands (noting the respective defaults of 1 and 0 and the implicit presence of an identity mapping for each unit identified as the value of a <code>baseUnit</code>).</p>
<p>Two units are convertible if and only if they share the same <code>baseUnit</code> value in CLDR. A unit that appears as a <code>baseUnit</code> value has an implicit identity conversion (<code>factor</code> 1, <code>offset</code> 0).</p>
<emu-note>
<p>In <code>factor</code> and <code>offset</code> expressions, <code>*</code> multiplication binds more tightly than <code>/</code> division, and constants defined by <code>&lt;unitConstant&gt;</code> elements are valid operands.</p>
Expand Down Expand Up @@ -434,14 +461,6 @@ location: https://github.com/tc39/proposal-amount/
<emu-alg>
1. Let _O_ be the *this* value.
1. Perform ? RequireInternalSlot(_O_, [[AmountValue]]).
1. Let _sourceUnit_ be _O_.[[Unit]].
1. If _sourceUnit_ is *undefined*, throw a *TypeError* exception.
1. Let _validatedOpts_ be ? GetAmountConvertToOptions(_options_).
1. Let _targetUnit_ be _validatedOpts_.[[Unit]].
1. If _targetUnit_ is *undefined*, throw a *TypeError* exception.
1. Let _roundingMode_ be _validatedOpts_.[[RoundingMode]].
1. Let _fractionDigits_ be _validatedOpts_.[[FractionDigits]].
1. Let _significantDigits_ be _validatedOpts_.[[SignificantDigits]].
1. Let _v_ be _O_.[[AmountValue]].
1. If _v_ is a Number, then
1. Let _sourceValue_ be _v_.
Expand All @@ -450,9 +469,16 @@ location: https://github.com/tc39/proposal-amount/
1. Else,
1. Assert: _v_ is a String.
1. Let _sourceValue_ be StringToNumber(_v_).
1. Let _sourceUnit_ be _O_.[[Unit]].
1. If _sourceUnit_ is *undefined*, throw a *TypeError* exception.
1. Let _opts_ be ? GetAmountConvertToOptions(_sourceValue_, _sourceUnit_, _options_).
1. Let _targetUnit_ be _opts_.[[Unit]].
1. Let _convertedValue_ be ? ConvertUnitValue(_sourceValue_, _sourceUnit_, _targetUnit_).
1. Let _result_ be OrdinaryObjectCreate(%Amount.prototype%, « [[AmountValue]], [[Unit]] »).
1. Let _fractionDigits_ be _opts_.[[FractionDigits]].
1. Let _significantDigits_ be _opts_.[[SignificantDigits]].
1. If _convertedValue_ is finite and (_fractionDigits_ is not *undefined* or _significantDigits_ is not *undefined*), then
1. Let _roundingMode_ be _opts_.[[RoundingMode]].
1. Let _formatter_ be CreateFormatterObject(_roundingMode_, _fractionDigits_, _fractionDigits_, _significantDigits_, _significantDigits_, *undefined*).
1. Let _formatted_ be FormatNumericToString(_formatter_, ℝ(_convertedValue_), 0).
1. Let _formattedMV_ be ! ToIntlMathematicalValue(_formatted_.[[FormattedString]]).
Expand Down
Loading