Skip to content

Commit 309f7a4

Browse files
committed
Merge branch 'main' into fix-fallback-also
2 parents 83d8011 + 51b0d5d commit 309f7a4

File tree

15 files changed

+975
-73
lines changed

15 files changed

+975
-73
lines changed

exploration/number-selection.md

Lines changed: 187 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Selection on Numerical Values
22

3-
Status: **Accepted**
3+
Status: **Re-Opened**
44

55
<details>
66
<summary>Metadata</summary>
@@ -13,6 +13,7 @@ Status: **Accepted**
1313
<dt>Pull Request</dt>
1414
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/471">#471</a></dd>
1515
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/621">#621</a></dd>
16+
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/859">#859</a></dd>
1617
</dl>
1718
</details>
1819

@@ -53,6 +54,21 @@ Both JS and ICU PluralRules implementations provide for determining the plural c
5354
of a range based on its start and end values.
5455
Range-based selectors are not initially considered here.
5556

57+
In <a href="https://github.com/unicode-org/message-format-wg/pull/842">PR #842</a>
58+
@eemeli points out a number of gaps or infelicities in the current specification
59+
and there was extensive discussion of how to address these gaps.
60+
61+
The `key` for exact numeric match in a variant has to be a string.
62+
The format of such strings, therefore, has to be specified if messages are to be portable and interoperable.
63+
In LDML45 Tech Preview we selected JSON's number serialization as a source for `key` values.
64+
The JSON serialization is ambiguous, in that a given number value might be serialized validly in more than one way:
65+
```
66+
123
67+
123.0
68+
1.23E2
69+
... etc...
70+
```
71+
5672
## Use-Cases
5773

5874
As a user, I want to write messages that use the correct plural for
@@ -68,13 +84,71 @@ As a user, I want to write messages that mix exact matching and
6884
either plural or ordinal selection in a single message.
6985
> For example:
7086
>```
71-
>.match {$numRemaining}
72-
>0 {{You have no more chances remaining (exact match)}}
73-
>1 {{You have one more chance remaining (exact match)}}
87+
>.match $numRemaining
88+
>0 {{You have no more chances remaining (exact match)}}
89+
>1 {{You have one more chance remaining (exact match)}}
7490
>one {{You have {$numRemaining} chance remaining (plural)}}
75-
> * {{You have {$numRemaining} chances remaining (plural)}}
91+
>* {{You have {$numRemaining} chances remaining (plural)}}
7692
>```
7793
94+
As a user, I want the selector to match the options specified:
95+
```
96+
.local $num = {123.123 :number maximumFractionDigits=2 minimumFractionDigits=2}
97+
.match $num
98+
123.12 {{This matches}}
99+
120 {{This does not match}}
100+
123.123 {{This does not match}}
101+
1.23123E2 {{Does this match?}}
102+
* {{ ... }}
103+
```
104+
105+
Note that badly written keys just don't match, but we want users to be able to intuit whether a given set of keys will work or not.
106+
107+
```
108+
.local $num = {123.456 :integer}
109+
.match $num
110+
123.456 {{Should not match?}}
111+
123 {{Should match}}
112+
123.0 {{Should not match?}}
113+
* {{ ... }}
114+
```
115+
116+
There can be complications, which we might need to define. Consider:
117+
118+
```
119+
.local $num = {123.002 :number maximumFractionDigits=1 minimumFractionDigits=0}
120+
.match $num
121+
123.002 {{Should not match?}}
122+
123.0 {{Does minimumFractionDigits make this not match?}}
123+
123 {{Does minimumFractionDigits make this match?}}
124+
* {{ ... }}
125+
```
126+
127+
As an implementer, I am concerned about the cost of incorporating _options_ into the selector.
128+
This might be accomplished by building a "second formatter".
129+
Some implementations, such as ICU4J's, might use interfaces like `FormattedNumber` to feed the selector.
130+
Implementations might also apply options by modifying the number value of the _operand_
131+
(or shadowing the options effect on the value)
132+
133+
As a user, I want to be able to perform exact match using arbitrary digit numeric types where they are available.
134+
135+
As an implementer, I do **not** want to be required to provide or implement arbitrary precision
136+
numeric types not available in my platform.
137+
Programming/runtime environments vary widely in support of these types.
138+
MF2 should not prevent the implementation using, for example, `BigDecimal` or `BigInt` types
139+
and permit their use in MF2 messages.
140+
MF2 should not _require_ implementations to support such types where they do not exist.
141+
The problem of numeric type precision,
142+
which is implementation dependent,
143+
should not affect how message `key` values are specified.
144+
145+
> For example:
146+
>```
147+
>.local $num = {11111111111111.11111111111111 :number}
148+
>.match $num
149+
>11111111111111.11111111111111 {{This works on some implementations.}}
150+
>* {{... but not on others? ...}}
151+
>```
78152
79153
## Requirements
80154
@@ -278,7 +352,8 @@ but can cause problems in target locales that the original developer is not cons
278352
> considering other locale's need for a `one` plural:
279353
>
280354
> ```
281-
> .match {$var}
355+
> .input {$var :integer}
356+
> .match $var
282357
> 1 {{You have one last chance}}
283358
> one {{You have {$var} chance remaining}} // needed by languages such as Polish or Russian
284359
> // such locales typically require other keywords
@@ -290,7 +365,13 @@ but can cause problems in target locales that the original developer is not cons
290365
### Percent Style
291366
292367
When implementing `style=percent`, the numeric value of the operand
293-
MUST be divided by 100 for the purposes of formatting.
368+
MUST be multiplied by 100 for the purposes of formatting.
369+
370+
> For example,
371+
> ```
372+
> .local $percent = {1 :integer style=percent}
373+
> {{This formats as '100%' in the en-US locale: {$percent}}}
374+
> ```
294375
295376
### Selection
296377
@@ -416,7 +497,9 @@ To expand on the last of these,
416497
consider this message:
417498
418499
```
419-
.match {$count :plural minimumFractionDigits=1}
500+
.input {$count :number minimumFractionDigits=1}
501+
.local $selector = {$count :plural}
502+
.match $selector
420503
0 {{You have no apples}}
421504
1 {{You have exactly one apple}}
422505
* {{You have {$count :number minimumFractionDigits=1} apples}}
@@ -431,9 +514,9 @@ With the proposed design, this message would much more naturally be written as:
431514
432515
```
433516
.input {$count :number minimumFractionDigits=1}
434-
.match {$count}
435-
0 {{You have no apples}}
436-
1 {{You have exactly one apple}}
517+
.match $count
518+
0.0 {{You have no apples}}
519+
1.0 {{You have exactly one apple}}
437520
one {{You have {$count} apple}}
438521
* {{You have {$count} apples}}
439522
```
@@ -460,3 +543,96 @@ and they _might_ converge on some overlap that users could safely use across pla
460543
#### Cons
461544
462545
- No guarantees about interoperability for a relatively core feature.
546+
547+
## Alternatives Considered (`key` matching)
548+
549+
### Standardize the Serialization Forms
550+
551+
Modify the above exact match as follows.
552+
Note that this implementation is less restrictive than before, but still leaves some
553+
values that cannot be matched.
554+
> [!IMPORTANT]
555+
> The exact behavior of exact literal match is only defined for
556+
> a specific range of numeric values and does not support scientific notation.
557+
> Very large or very small numeric values will be difficult to perform
558+
> exact matching on.
559+
> Avoid depending on these types of keys in message selection.
560+
> [!IMPORTANT]
561+
> For implementations that do not have arbitrary precision numeric types
562+
> or operands that do not use these types,
563+
> it is possible to specify a key value that exceeds the precision
564+
> of the underlying type.
565+
> Such a key value will not work reliably or may not work at all
566+
> in such implementations.
567+
> Avoid depending on such keys values in message selection.
568+
Number literals in the MessageFormat 2 syntax use a subset of the
569+
[format defined for a JSON number](https://www.rfc-editor.org/rfc/rfc8259#section-6).
570+
The resolved value of an `operand` exactly matches a numeric literal `key`
571+
if, when the `operand` is serialized using this format
572+
the two strings are equal.
573+
```abnf
574+
number = [ "-" ] int [ fraction ]
575+
integer = "0" / [ "-" ] (digit19 *DIGIT)
576+
int = "0" / (digit19 *DIGIT)
577+
digit19 = %31-39 ; 1-9
578+
fraction = "." 1*DIGIT
579+
```
580+
If the function `:integer` is used or the `maximumFractionDigits` is 0,
581+
the production `integer` is used and any fractional amount is omitted,
582+
otherwise the `minimumFractionDigits` number of digits is produced,
583+
zero-filled as needed.
584+
The implementation applies the `maximumSignificantDigits` to the value
585+
being serialized.
586+
This might involve locally-specific rounding.
587+
The `minimumSignificantDigits` has no effect on the value produced for comparison.
588+
The option `signDisplay` has no effect on the value produced for comparison.
589+
> [!NOTE]
590+
> Implementations are not expected to implement this exactly as written,
591+
> as there are clearly optimizations that can be applied.
592+
> Here are some examples:
593+
> ```
594+
> .input {$num :integer}
595+
> .match $num
596+
> 0 {{The number 0}}
597+
> 1 {{The number 1}}
598+
> -1 {{The number -1}}
599+
> 1.0 {{This cannot match}}
600+
> 1.1 {{This cannot match}}
601+
> ```
602+
> ```
603+
> .input {$num :number maximumFractionDigits=2 minimumFractionDigits=2}
604+
> .match $num
605+
> 0 {{This does not match}}
606+
> 0.00 {{This matches the value 0}}
607+
> 0.0 {{This does not match}}
608+
> 0.000 {{This does not match}}
609+
> ```
610+
> ```
611+
> .input {$num :number minimumFractionDigits=2 maximumFractionDigits=5}
612+
> .match $num
613+
> 0.12 {{Matches the value 0.12}
614+
> 0.123 {{Matches the value 0.123}}
615+
> 0.12345 {{Matches the values 0.12345}}
616+
> 0.123456 {{Does not match}}
617+
> 0.12346 {{May match the value 0.123456 depending on local rounding mode?}}
618+
> ```
619+
> ```
620+
> .input {$num :number}
621+
> -0 {{Error: Bad Variant Key}}
622+
> -99 {{The value -99}}
623+
> 1111111111111111111111111111 {{Might exceed the size of local integer type, but is valid}}
624+
> 11111111111111.1111111111111 {{Might exceed local floating point precision, but is valid}}
625+
> 1.23e-37 {{Error: Bad Variant Key}}
626+
> ```
627+
628+
629+
630+
### Compare numeric values
631+
632+
This is the design proposed in #842.
633+
634+
This modifies the key-match algorithm to use implementation-defined numeric value exact match:
635+
636+
> 1. Let `exact` be the numeric value represented by `key`.
637+
> 1. If `value` and `exact` are numerically equal, then
638+

0 commit comments

Comments
 (0)