Skip to content

Comments

Normative: Increase limits on Intl MV and explicitly limit significant digits#1022

Open
sffc wants to merge 2 commits intotc39:mainfrom
sffc:intlmv-limits
Open

Normative: Increase limits on Intl MV and explicitly limit significant digits#1022
sffc wants to merge 2 commits intotc39:mainfrom
sffc:intlmv-limits

Conversation

@sffc
Copy link
Contributor

@sffc sffc commented Aug 9, 2025

See #1017

The intent of this PR is the following. In this table, 99~9 stands in for 9999 digit 9s, and 999~9 stands in for 10000 digit 9s.

Input String MV Resolved MV Comments
1e9999 1e9999
99~9e9999 99~9e9999
99~9.999~9e9999 99~9.999~9e9999 Largest in-bounds value
1e10000 Smallest out-of-bounds value
1e-10000 1e-10000 Smallest in-bounds value
9e-10001 0
0.999~9 0.999~9
0.999~98 0.999~9 Truncate such that the least significant digit is at 1e-10000
1.9e-10000 1e-10000 ^
1.234e-9998 1.23e-9998 ^

@sffc
Copy link
Contributor Author

sffc commented Aug 9, 2025

@sffc
Copy link
Contributor Author

sffc commented Aug 9, 2025

Just a note on the significant digits truncation: this could technically change the behavior of numbers so close to the edge of a rounding boundary where the discriminator is at a position less than -10000; for example, 2.5 followed by 10000 zeros followed by 1, with half-even rounding. In the current spec, that number should round up to 3, but in this new spec, that number should round down to 2 since the significant digit is dropped during parsing, not during rounding. I think people like to call this "double rounding".

If this is a problem (it might not be: 10000 digits is beyond all known use cases), it can be fixed by performing a bigger refactoring of the spec in order to avoid the spec-required double rounding, but actually it is easier to implement if the truncation happens during parsing.

@sffc sffc moved this to Priority Issues in ECMA-402 Meeting Topics Aug 13, 2025
@eemeli
Copy link
Member

eemeli commented Aug 14, 2025

Could someone confirm that this approach would not cause any issues with implementations that rely on ICU4C or ICU4J?

@sffc
Copy link
Contributor Author

sffc commented Aug 14, 2025

ICU4C (and ICU4J) have a similar representation as ICU4X but use int32_t for the fields.

https://github.com/unicode-org/icu/blob/3a66f8c5fecfc3f0ee427c12b90966bac1b02d92/icu4c/source/i18n/number_decimalquantity.h#L361

@sffc
Copy link
Contributor Author

sffc commented Aug 15, 2025

@sffc
Copy link
Contributor Author

sffc commented Aug 15, 2025

@erights It was suggested that we bring this PR to TG3. Could it be added to an upcoming TG3 agenda?

@erights
Copy link

erights commented Aug 15, 2025 via email

@sffc
Copy link
Contributor Author

sffc commented Sep 11, 2025

1. If _rounded_ is *+∞*<sub>𝔽</sub>, return ~positive-infinity~.
1. If _rounded_ is *+0*<sub>𝔽</sub> and _intlMV_ &lt; 0, return ~negative-zero~.
1. If _rounded_ is *+0*<sub>𝔽</sub>, return 0.
1. Let _e_ be the integer such that 10<sup>_e_</sup> ≤ abs(_intlMV_) &lt; 10<sup>_e_ + 1</sup>.

Choose a reason for hiding this comment

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

Choosing such an e is impossible if intlMV is zero. To fix it, handle the zero case separately.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
1. Let _e_ be the integer such that 10<sup>_e_</sup> ≤ abs(_intlMV_) &lt; 10<sup>_e_ + 1</sup>.
1. If _intlMV_ = 0, return 0.
1. Let _e_ be floor(log10(abs(_intlMV_))).

@waldemarhorwat
Copy link

The description above doesn't match what the algorithm is doing. I assume this is due to typos in the description? I don't see why 10e9999 would be in-bounds while 1e10000 would be out of bounds (it's the same mathematical value), which is what the description above is currently stating.

The algorithm changes look good, once the bug when intlMV is zero is fixed.

@sffc sffc moved this from Priority Issues to Previously Discussed in ECMA-402 Meeting Topics Oct 8, 2025
anba added a commit to anba/test262 that referenced this pull request Dec 11, 2025
Use "1e10000" from <tc39/ecma402#1022> as the
limit so we don't have to modify this test after the PR has been merged.

Note: "1e309" is the smallest decimal string number outside the limits
before that PR.
Copy link
Member

@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.

See the below suggestions about how to dramatically simplify this algorithm.

But I agree with @waldemarhorwat about the description being inaccurate... it is not constraining significant digits as such but rather independently constraining whole digits and fractional digits each to a count of 10_000, such that larger whole parts support more total significant digits (e.g., a whole part of 0 implies at most 10_000 significant digits [all fractional] while a whole part of 12_345 is already 5 significant digits to which can be appended another 10_000 significant digits [all fractional] and a whole part of 1e10000 - 1 would already have 10_000 significant digits before even considering a fractional part).

I'm fine with this algorithm (ideally as an easier-to-comprehend simplification), but a constraint on significant digits would have a different definition and very different behavior (such behavior would in fact be like that of IEEE 754 numbers, losing precision near the top and bottom of the range).

1. If _rounded_ is *+∞*<sub>𝔽</sub>, return ~positive-infinity~.
1. If _rounded_ is *+0*<sub>𝔽</sub> and _intlMV_ &lt; 0, return ~negative-zero~.
1. If _rounded_ is *+0*<sub>𝔽</sub>, return 0.
1. Let _e_ be the integer such that 10<sup>_e_</sup> ≤ abs(_intlMV_) &lt; 10<sup>_e_ + 1</sup>.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
1. Let _e_ be the integer such that 10<sup>_e_</sup> ≤ abs(_intlMV_) &lt; 10<sup>_e_ + 1</sup>.
1. If _intlMV_ = 0, return 0.
1. Let _e_ be floor(log10(abs(_intlMV_))).

Comment on lines +1663 to +1668
1. If _e_ ≥ 10000, then
1. If _intlMV_ &lt; 0, return ~negative-infinity~.
1. Else, return ~positive-infinity~.
1. If _e_ &lt; -10000, then
1. If _intlMV_ &lt; 0, return ~negative-zero~.
1. Else, return 0.
Copy link
Member

Choose a reason for hiding this comment

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

Aligning with tc39/ecma262#3733:

Suggested change
1. If _e_ ≥ 10000, then
1. If _intlMV_ &lt; 0, return ~negative-infinity~.
1. Else, return ~positive-infinity~.
1. If _e_ &lt; -10000, then
1. If _intlMV_ &lt; 0, return ~negative-zero~.
1. Else, return 0.
1. If _e_ ≥ 10000, then
1. If _intlMV_ &lt; 0, return ~negative-infinity~; else return ~positive-infinity~.
1. If _e_ &lt; -10000, then
1. If _intlMV_ &lt; 0, return ~negative-zero~; else return 0.

Comment on lines +1669 to +1673
1. Let _q_ be the largest integer such that _intlMV_ × 10<sup>-_q_</sup> is an integer.
1. If _q_ &lt; -10000, then
1. Let _truncated_ be the largest mathematical value such that _truncated_ × 10<sup>10000</sup> is an integer and _truncated_ &lt; abs(_intlMV_).
1. If _intlMV_ &lt; 0, return -_truncated_.
1. Else, return _truncated_.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
1. Let _q_ be the largest integer such that _intlMV_ × 10<sup>-_q_</sup> is an integer.
1. If _q_ &lt; -10000, then
1. Let _truncated_ be the largest mathematical value such that _truncated_ × 10<sup>10000</sup> is an integer and _truncated_ &lt; abs(_intlMV_).
1. If _intlMV_ &lt; 0, return -_truncated_.
1. Else, return _truncated_.
1. Let _scaled_ be _intlMV_ × 10<sup>10000</sup>.
1. If _scaled_ is not an integer, then
1. Return truncate(_scaled_) / 10<sup>10000</sup>.

This can also be generalized to subsume the preceding step:

          1. If _e_ ≥ 10000, then
            1. If _intlMV_ &lt; 0, return ~negative-infinity~; else return ~positive-infinity~.
          1. Let _scaled_ be _intlMV_ × 10<sup>10000</sup>.
          1. If _scaled_ is not an integer, then
            1. Let _truncated_ be truncate(_scaled_) / 10<sup>10000</sup>.
            1. If _truncated_ = 0 and _intlMV_ &lt; 0, return ~negative-zero~.
            1. Return _truncated_.

@sffc
Copy link
Contributor Author

sffc commented Feb 19, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Previously Discussed

Development

Successfully merging this pull request may close these issues.

6 participants