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
5354of a range based on its start and end values.
5455Range-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
5874As 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
6884either 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
292367When 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,
416497consider this message:
417498
418499```
419- .match {$count : plural minimumFractionDigits=1}
500+ .input {$count : number minimumFractionDigits=1}
501+ .local $selector = {$count : plural }
502+ .match $selector
4205030 {{You have no apples}}
4215041 {{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}}
437520one {{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