Skip to content

Add limits to Intl MV #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 28, 2023
Merged

Add limits to Intl MV #128

merged 3 commits into from
Jan 28, 2023

Conversation

sffc
Copy link
Collaborator

@sffc sffc commented Jan 12, 2023

Fixes #98

Replaces #117

@@ -1592,7 +1592,12 @@
1. Let _text_ be ! StringToCodePoints(_str_).
1. Let _literal_ be ParseText(_text_, |StringNumericLiteral|).
1. If _literal_ is a List of errors, return ~not-a-number~.
1. Return the StringIntlMV of _literal_.
1. Let _intlMV_ be the StringIntlMV of _literal_.
1. If _intlMV_ is a mathematical value and _intlMV_ > ℝ(Number.MAX_VALUE), the implementation may set _intlMV_ to ~positive-infinity~.
Copy link

Choose a reason for hiding this comment

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

Nit: % to refer to the original value of Number.MAX_VALUE. (Also for the other references below.)

Suggested change
1. If _intlMV_ is a mathematical value and _intlMV_ > ℝ(Number.MAX_VALUE), the implementation may set _intlMV_ to ~positive-infinity~.
1. If _intlMV_ is a mathematical value and _intlMV_ > ℝ(%Number.MAX_VALUE%), the implementation may set _intlMV_ to ~positive-infinity~.

Copy link
Contributor

@gibson042 gibson042 left a comment

Choose a reason for hiding this comment

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

I'm not comfortable with the amount of variation this permits within and across implementations. Behavior should really be more predictable.

@sffc
Copy link
Collaborator Author

sffc commented Jan 14, 2023

I'm not comfortable with the amount of variation this permits within and across implementations. Behavior should really be more predictable.

Do you have a counter proposal? Anything I can do to help align on the objective?

@gibson042
Copy link
Contributor

Well, can we just remove all variance?

-        1. If _intlMV_ is a mathematical value and _intlMV_ > ℝ(%Number.MAX_VALUE%), the implementation may set _intlMV_ to ~positive-infinity~.
-        1. If _intlMV_ is a mathematical value and _intlMV_ < ℝ(-%Number.MAX_VALUE%), the implementation may set _intlMV_ to ~negative-infinity~.
-        1. If _intlMV_ is a mathematical value and 0 < _intlMV_ < ℝ(%Number.MIN_VALUE%), the implementation may set _intlMV_ to 0.
-        1. If _intlMV_ is a mathematical value and 0 > _intlMV_ > ℝ(-%Number.MIN_VALUE%), the implementation may set _intlMV_ to ~negative-zero~.
+        1. If _intlMV_ is a mathematical value and _intlMV_ > ℝ(%Number.MAX_VALUE%), set _intlMV_ to ~positive-infinity~.
+        1. If _intlMV_ is a mathematical value and _intlMV_ < ℝ(-%Number.MAX_VALUE%), set _intlMV_ to ~negative-infinity~.
+        1. If _intlMV_ is a mathematical value and 0 < _intlMV_ < ℝ(%Number.MIN_VALUE%), set _intlMV_ to 0.
+        1. If _intlMV_ is a mathematical value and 0 > _intlMV_ > ℝ(-%Number.MIN_VALUE%), set _intlMV_ to ~negative-zero~.

If not, then I think the best alternative is introducing symmetric implementation-defined bounds:

        1. If _intlMV_ is a mathematical value and abs(_intlMV_) > MVMagnitudeMaximum(), then
          1. If _intlMV_ > 0, set _intlMV_ to ~positive-infinity~. Otherwise, set _intlMV_ to ~negative-infinity~.
        1. If _intlMV_ is a mathematical value and abs(_intlMV_) < MVMagnitudeMinimum(), then
          1. If _intlMV_ ≥ 0, set _intlMV_ to 0. Otherwise, set _intlMV_ to ~negative-zero~.

And in either case, I think it is also necessary to bound the precision of these values (e.g., implementations should be allowed—if not required—to treat `1${"0".repeat(100)}.${"0".repeat(100)}1` [1000…000.000…0001] identically to `1${"0".repeat(100)}` [1000…000]).

@romulocintra
Copy link
Member

Test262 -> tc39/test262#3772

@sffc
Copy link
Collaborator Author

sffc commented Jan 18, 2023

@gibson042 The rationale for making soft lower bounds is

  1. This is already the web reality.
  2. It is expensive for browsers to perform these high-order computations in the MV space. For example, V8 fully defers to whatever ICU4C does in this area; the V8 engine does not contain its own BigDecimal-like type to perform this logic.
  3. The spec already allows browsers a bit of leeway in the details of how they handle IEEE floating point arithmetic; this is in exactly the same vein.

And in either case, I think it is also necessary to bound the precision of these values

ECMA-402 requires all numbers to be rounded to a certain number of fraction or significant digits, not more than 20, so that already serves as a de-facto bound on the precision of these MVs.

@gibson042
Copy link
Contributor

  1. This is already the web reality.

I'm saying that users are harmed by the same input being treated as infinite in one implementation but not in another. Why should this proposal not address that?

  1. It is expensive for browsers to perform these high-order computations in the MV space. For example, V8 fully defers to whatever ICU4C does in this area; the V8 engine does not contain its own BigDecimal-like type to perform this logic.

Implementers are strictly lower in the priority of constituencies than both authors and users. If V8 and/or ICU are failing to meet the needs of the latter groups, then we should push for fixes there (e.g., ICU exposing configuration of limits).

  1. The spec already allows browsers a bit of leeway in the details of how they handle IEEE floating point arithmetic; this is in exactly the same vein.

I believe you're referring to https://tc39.es/ecma262/multipage/overview.html#implementation-approximated , in which case I dispute the analogy because the difference between e.g. 253 and 253 + 1 is one of degree while the difference between e.g. 10**308 and Infinity is one of kind.

ECMA-402 requires all numbers to be rounded to a certain number of fraction or significant digits, not more than 20, so that already serves as a de-facto bound on the precision of these MVs.

Hmm, that's true but it seems brittle.

@sffc
Copy link
Collaborator Author

sffc commented Jan 18, 2023

@anba How do you feel about this being a hard limit?

@sffc
Copy link
Collaborator Author

sffc commented Jan 19, 2023

  1. This is already the web reality.

I'm saying that users are harmed by the same input being treated as infinite in one implementation but not in another. Why should this proposal not address that?

I argue that the user harm is minimal. The primary motivation behind the Intl MV change was to support large, precise amounts of currency as required by amounts in bitcoin, yen, etc. Supporting more digits than a Number can represent was the pain point. I'm not aware of strong motivating use cases involving large power of 10 exponents.

  1. It is expensive for browsers to perform these high-order computations in the MV space. For example, V8 fully defers to whatever ICU4C does in this area; the V8 engine does not contain its own BigDecimal-like type to perform this logic.

Implementers are strictly lower in the priority of constituencies than both authors and users. If V8 and/or ICU are failing to meet the needs of the latter groups, then we should push for fixes there (e.g., ICU exposing configuration of limits).

While I agree with this in the general sense, when it comes to edge cases, I feel that implementors carry more weight.

This change is motivated by implementation constraints that were discovered in Stage 3. I worry that a hard limit is trading one implementation constraint (internal limits on the size of the number) with another (having to interpret the size of the number).

  1. The spec already allows browsers a bit of leeway in the details of how they handle IEEE floating point arithmetic; this is in exactly the same vein.

I believe you're referring to https://tc39.es/ecma262/multipage/overview.html#implementation-approximated , in which case I dispute the analogy because the difference between e.g. 253 and 253 + 1 is one of degree while the difference between e.g. 10**308 and Infinity is one of kind.

Acknowledged

ECMA-402 requires all numbers to be rounded to a certain number of fraction or significant digits, not more than 20, so that already serves as a de-facto bound on the precision of these MVs.

Hmm, that's true but it seems brittle.

Why is it brittle?

The effective requirement of implementations is that they support ~330 significant digits (~310 before the decimal separator and another ~20 after it for the largest valid value of maximumSignificantDigits). This is another edge case, and it's possible that we don't need so many digits, but any other number of digits is just an arbitrary choice, and it again treads into the territory of implementation challenge.

@anba
Copy link

anba commented Jan 19, 2023

@anba How do you feel about this being a hard limit?

Is there an easy way to compare a string which contains a number in decimal representation against ℝ(Number.MIN_VALUE) and ℝ(Number.MAX_VALUE)? IOW is there an easy way to get the exact decimal representation of ℝ(Number.MIN_VALUE) and ℝ(Number.MAX_VALUE)? (When the exact decimal representation is known, we can easily compare the normalised forms against each other. At least as long as we're okay with having to parse the string as exact decimal numbers.)

This is the behaviour before NF v3:

js> new Intl.NumberFormat("en", {minimumSignificantDigits: 1}).format(Number.MIN_VALUE)
"0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"
js> Number.MIN_VALUE.toPrecision(100)
"4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359924e-324"
js> new Intl.NumberFormat("en", {minimumSignificantDigits: 1}).format(Number.MIN_VALUE.toPrecision(100))
"0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"

When positive numbers smaller than ℝ(Number.MIN_VALUE) are changed to zero, inputs like "4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923e-324" ("...4e-324" was changed to "...3e-324") will have to return "0".

I don't think we care too much about the behaviour of too small or too large values. What if we simply parse the input as a Number and when the result is ±Infinity, ±Number.MAX_VALUE, or ±Number.MIN_VALUE, take the Number value as the actual input?

I think this could be expressed through the RoundMVResult operation:

  1. Let intlMV the StringIntlMV of literal.
  2. If intlMV is a mathematical value, then
    a. Let rounded be RoundMVResult(abs(intlMV)).
    b. If rounded is +∞𝔽 and intlMV < 0, return negative-infinity.
    c. If rounded is +∞𝔽, return positive-infinity.
    d. If rounded is +0𝔽 and intlMV < 0, return negative-zero.
    e. If rounded is +0𝔽, return 0.
    f. If rounded is +%Number.MAX_VALUE% or +%Number.MIN_VALUE%, then
        i. If intlMV < 0, return -ℝ(rounded).
        ii. Return ℝ(rounded).
  3. Return intlMV.

Copy link
Contributor

@FrankYFTang FrankYFTang left a comment

Choose a reason for hiding this comment

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

I like the idea of rounding before compare against the limit.

@sffc sffc merged commit a0cfabb into master Jan 28, 2023
@sffc sffc deleted the intl-mv-limit branch January 28, 2023 01:43
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request Jun 7, 2023
…t limits. r=dminor

Apply the Intl mathematical value limits from [1] to our code. The minimum and
maximum exponents are now limited by `Number.MIN_VALUE` resp. `Number.MAX_VALUE`,
which are both way below our previous limit of `9'999'999`.

Also removes the BigInt exponent check, because `JS::BigInt::MaxDigitLength`
already ensured that the maximum exponent has a reasonable value for BigInts.

Updates the tests to ensure new limits are correctly applied.


[1] tc39/proposal-intl-numberformat-v3#128

Differential Revision: https://phabricator.services.mozilla.com/D179942
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Limit exponent of intl mathematical value
5 participants