Issue #2350 - Part 1: Apply "minimumIntegerDigits" to number-formatters with fraction-digits.

Implements the changes to `SetNumberFormatDigitOptions` and
`FormatNumberToString`, except for the support of the new
"compact-rounding" mode.
This also includes a few minor fixes for edge cases (like -0 vs. 0)
This commit is contained in:
Moonchild 2023-10-19 10:21:54 +02:00 committed by roytam1
commit be81bae5b7
5 changed files with 111 additions and 69 deletions

View file

@ -698,29 +698,13 @@ function DefaultNumberOption(value, minimum, maximum, fallback) {
* Number value, checks whether it is in the allowed range, and fills in a
* fallback value if necessary.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.10.
* Spec: ECMAScript Internationalization API Specification, 9.2.12.
*/
function GetNumberOption(options, property, minimum, maximum, fallback) {
assert(typeof minimum === "number", "GetNumberOption");
assert(typeof maximum === "number", "GetNumberOption");
assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption");
// Step 1.
var value = options[property];
// Step 2.
if (value !== undefined) {
value = ToNumber(value);
if (Number_isNaN(value) || value < minimum || value > maximum)
ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, value);
return std_Math_floor(value);
}
// Step 3.
return fallback;
// Steps 1-3.
return DefaultNumberOption(options[property], minimum, maximum, fallback);
}
// Symbols in the self-hosting compartment can't be cloned, use a separate
// object to hold the actual symbol value.
// TODO: Can we add support to clone symbols?

View file

@ -353,10 +353,6 @@ NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
return nullptr;
uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
return nullptr;
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
return nullptr;
@ -367,6 +363,11 @@ NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
return nullptr;
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
return nullptr;
uUseGrouping = value.toBoolean();

View file

@ -55,8 +55,14 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
// Step 22.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
if ("minimumFractionDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
// actual presence (versus undefined-ness) of these properties.
assert("maximumFractionDigits" in lazyNumberFormatData, "min/max frac digits mismatch");
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
}
if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
@ -122,33 +128,74 @@ function UnwrapNumberFormat(nf, methodName) {
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
*/
function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) {
function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault, mxfdDefault) {
// We skip step 1 because we set the properties on a lazyData object.
// Steps 2-4.
assert(IsObject(options), "SetNumberFormatDigitOptions");
assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
// Steps 5-8.
const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
assert(typeof mxfdDefault === "number", "SetNumberFormatDigitOptions");
assert(mnfdDefault <= mxfdDefault, "SetNumberFormatDigitOptions");
// Steps 9-10.
// Steps 5-9.
const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
let mnfd = options.minimumFractionDigits;
let mxfd = options.maximumFractionDigits;
let mnsd = options.minimumSignificantDigits;
let mxsd = options.maximumSignificantDigits;
// Steps 9-11.
// Step 10.
lazyData.minimumIntegerDigits = mnid;
lazyData.minimumFractionDigits = mnfd;
lazyData.maximumFractionDigits = mxfd;
// Step 11.
if (mnsd !== undefined || mxsd !== undefined) {
// Step 11.a (Omitted).
// Step 11.b.
mnsd = DefaultNumberOption(mnsd, 1, 21, 1);
// Step 11.c.
mxsd = DefaultNumberOption(mxsd, mnsd, 21, 21);
// Step 11.d.
lazyData.minimumSignificantDigits = mnsd;
// Step 11.e.
lazyData.maximumSignificantDigits = mxsd;
}
// Step 12.
if (mnsd !== undefined || mxsd !== undefined) {
mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
lazyData.minimumSignificantDigits = mnsd;
lazyData.maximumSignificantDigits = mxsd;
else if (mnfd !== undefined || mxfd !== undefined) {
// Step 12.a (Omitted).
// Step 12.b.
mnfd = DefaultNumberOption(mnfd, 0, 20, mnfdDefault);
// Step 12.c.
const mxfdActualDefault = std_Math_max(mnfd, mxfdDefault);
// Step 12.d.
mxfd = DefaultNumberOption(mxfd, mnfd, 20, mxfdActualDefault);
// Step 12.e.
lazyData.minimumFractionDigits = mnfd;
// Step 12.f.
lazyData.maximumFractionDigits = mxfd;
}
// Step 13 (TODO: Not yet implemented).
// Step 14.
else {
// Step 14.a (Omitted).
// Step 14.b.
lazyData.minimumFractionDigits = mnfdDefault;
// Step 14.c.
lazyData.maximumFractionDigits = mxfdDefault;
}
}
@ -291,19 +338,16 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
// Steps 20-22.
SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
// Step 25.
if (lazyNumberFormatData.maximumFractionDigits === undefined) {
let mxfdDefault = s === "currency"
? cDigits
: s === "percent"
? 0
: 3;
lazyNumberFormatData.maximumFractionDigits =
std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
// Steps 22-25.
var mnfdDefault, mxfdDefault;
if (s === "currency") {
mnfdDefault = cDigits;
mxfdDefault = cDigits;
} else {
mnfdDefault = 0;
mxfdDefault = s === "percent" ? 0 : 3;
}
SetNumberFormatDigitOptions(lazyNumberFormatData, options, mnfdDefault, mxfdDefault);
// Steps 23.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
@ -520,8 +564,6 @@ function Intl_NumberFormat_resolvedOptions() {
numberingSystem: internals.numberingSystem,
style: internals.style,
minimumIntegerDigits: internals.minimumIntegerDigits,
minimumFractionDigits: internals.minimumFractionDigits,
maximumFractionDigits: internals.maximumFractionDigits,
useGrouping: internals.useGrouping
};
@ -536,6 +578,15 @@ function Intl_NumberFormat_resolvedOptions() {
_DefineDataProperty(result, "currencyDisplay", internals.currencyDisplay);
}
// Min/Max fraction digits are either both present or not present at all.
assert(hasOwn("minimumFractionDigits", internals) ===
hasOwn("maximumFractionDigits", internals),
"minimumFractionDigits is present iff maximumFractionDigits is present");
if (hasOwn("minimumFractionDigits", internals)) {
_DefineDataProperty(result, "minimumFractionDigits", internals.minimumFractionDigits);
_DefineDataProperty(result, "maximumFractionDigits", internals.maximumFractionDigits);
}
// Min/Max significant digits are either both present or not at all.
assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals),

View file

@ -224,11 +224,6 @@ NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralR
return nullptr;
uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
return nullptr;
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
return nullptr;
@ -240,6 +235,11 @@ NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralR
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
return nullptr;
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* nf = unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
if (U_FAILURE(status)) {

View file

@ -53,8 +53,12 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
// Step 9.
internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
if ("minimumFractionDigits" in lazyPluralRulesData) {
assert("maximumFractionDigits" in lazyPluralRulesData, "min/max frac digits mismatch");
internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
}
if ("minimumSignificantDigits" in lazyPluralRulesData) {
assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
@ -149,14 +153,8 @@ function InitializePluralRules(pluralRules, locales, options) {
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 9.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
// Step 12.
if (lazyPluralRulesData.maximumFractionDigits === undefined) {
lazyPluralRulesData.maximumFractionDigits =
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
// Steps 11-12.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0, 3);
// Step 15.
//
@ -246,11 +244,19 @@ function Intl_PluralRules_resolvedOptions() {
type: internals.type,
pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
minimumIntegerDigits: internals.minimumIntegerDigits,
minimumFractionDigits: internals.minimumFractionDigits,
maximumFractionDigits: internals.maximumFractionDigits,
};
// Min/Max significant digits are either both present or not at all.
// Min/Max fraction digits are either both present or not present at all.
assert(hasOwn("minimumFractionDigits", internals) ===
hasOwn("maximumFractionDigits", internals),
"minimumFractionDigits is present iff maximumFractionDigits is present");
if (hasOwn("minimumFractionDigits", internals)) {
_DefineDataProperty(result, "minimumFractionDigits", internals.minimumFractionDigits);
_DefineDataProperty(result, "maximumFractionDigits", internals.maximumFractionDigits);
}
// Min/Max significant digits are either both present or not present at all.
assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals),
"minimumSignificantDigits is present iff maximumSignificantDigits is present");