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 * Number value, checks whether it is in the allowed range, and fills in a
* fallback value if necessary. * 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) { function GetNumberOption(options, property, minimum, maximum, fallback) {
assert(typeof minimum === "number", "GetNumberOption"); // Steps 1-3.
assert(typeof maximum === "number", "GetNumberOption"); return DefaultNumberOption(options[property], minimum, maximum, fallback);
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;
} }
// Symbols in the self-hosting compartment can't be cloned, use a separate // Symbols in the self-hosting compartment can't be cloned, use a separate
// object to hold the actual symbol value. // object to hold the actual symbol value.
// TODO: Can we add support to clone symbols? // TODO: Can we add support to clone symbols?

View file

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

View file

@ -55,8 +55,14 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
// Step 22. // Step 22.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits; 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) { if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
@ -122,33 +128,74 @@ function UnwrapNumberFormat(nf, methodName) {
* *
* Spec: ECMAScript Internationalization API Specification, 11.1.1. * 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. // We skip step 1 because we set the properties on a lazyData object.
// Steps 2-4. // Steps 2-4.
assert(IsObject(options), "SetNumberFormatDigitOptions"); assert(IsObject(options), "SetNumberFormatDigitOptions");
assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions"); assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
// Steps 5-8. assert(typeof mxfdDefault === "number", "SetNumberFormatDigitOptions");
const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); assert(mnfdDefault <= mxfdDefault, "SetNumberFormatDigitOptions");
const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
// 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 mnsd = options.minimumSignificantDigits;
let mxsd = options.maximumSignificantDigits; let mxsd = options.maximumSignificantDigits;
// Steps 9-11. // Step 10.
lazyData.minimumIntegerDigits = mnid; 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. // Step 12.
if (mnsd !== undefined || mxsd !== undefined) { else if (mnfd !== undefined || mxfd !== undefined) {
mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1); // Step 12.a (Omitted).
mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
lazyData.minimumSignificantDigits = mnsd; // Step 12.b.
lazyData.maximumSignificantDigits = mxsd; 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") if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd; lazyNumberFormatData.currencyDisplay = cd;
// Steps 20-22. // Steps 22-25.
SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0); var mnfdDefault, mxfdDefault;
if (s === "currency") {
// Step 25. mnfdDefault = cDigits;
if (lazyNumberFormatData.maximumFractionDigits === undefined) { mxfdDefault = cDigits;
let mxfdDefault = s === "currency" } else {
? cDigits mnfdDefault = 0;
: s === "percent" mxfdDefault = s === "percent" ? 0 : 3;
? 0
: 3;
lazyNumberFormatData.maximumFractionDigits =
std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
} }
SetNumberFormatDigitOptions(lazyNumberFormatData, options, mnfdDefault, mxfdDefault);
// Steps 23. // Steps 23.
var g = GetOption(options, "useGrouping", "boolean", undefined, true); var g = GetOption(options, "useGrouping", "boolean", undefined, true);
@ -520,8 +564,6 @@ function Intl_NumberFormat_resolvedOptions() {
numberingSystem: internals.numberingSystem, numberingSystem: internals.numberingSystem,
style: internals.style, style: internals.style,
minimumIntegerDigits: internals.minimumIntegerDigits, minimumIntegerDigits: internals.minimumIntegerDigits,
minimumFractionDigits: internals.minimumFractionDigits,
maximumFractionDigits: internals.maximumFractionDigits,
useGrouping: internals.useGrouping useGrouping: internals.useGrouping
}; };
@ -536,6 +578,15 @@ function Intl_NumberFormat_resolvedOptions() {
_DefineDataProperty(result, "currencyDisplay", internals.currencyDisplay); _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. // Min/Max significant digits are either both present or not at all.
assert(hasOwn("minimumSignificantDigits", internals) === assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals), hasOwn("maximumSignificantDigits", internals),

View file

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

View file

@ -53,8 +53,12 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
// Step 9. // Step 9.
internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits; 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) { if ("minimumSignificantDigits" in lazyPluralRulesData) {
assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch"); 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"); const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type; lazyPluralRulesData.type = type;
// Step 9. // Steps 11-12.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0); SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0, 3);
// Step 12.
if (lazyPluralRulesData.maximumFractionDigits === undefined) {
lazyPluralRulesData.maximumFractionDigits =
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
// Step 15. // Step 15.
// //
@ -246,11 +244,19 @@ function Intl_PluralRules_resolvedOptions() {
type: internals.type, type: internals.type,
pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0), pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
minimumIntegerDigits: internals.minimumIntegerDigits, 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) === assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals), hasOwn("maximumSignificantDigits", internals),
"minimumSignificantDigits is present iff maximumSignificantDigits is present"); "minimumSignificantDigits is present iff maximumSignificantDigits is present");