diff --git a/spec/registry.md b/spec/registry.md index 86dbb037f..25d1ff033 100644 --- a/spec/registry.md +++ b/spec/registry.md @@ -378,6 +378,77 @@ together with the resolved options' values. The _function_ `:integer` performs selection as described in [Number Selection](#number-selection) below. +### The `:math` function + +The function `:math` is a selector and formatter for matching or formatting +numeric values to which a mathematical operation has been applied. + +> This function is useful for selection and formatting of values that +> differ from the input value by a specified amount. +> For example, it can be used in a message such as this: +> ``` +> .input {$like_count :integer} +> .local $others_count = {$like_count :math subtract=1} +> .match $like_count $others_count +> 0 * {{Your post has no likes.}} +> 1 * {{{$name} liked your post.}} +> * 1 {{{$name} and one other person liked your post.}} +> * * {{{$name} and {$others_count} other people liked your post.}} +> ``` + +#### Operands + +The function `:math` requires a [Number Operand](#number-operands) as its _operand_. + +#### Options + +The options on `:math` are exclusive with each other, +and exactly one option is always required. +The options do not have default values. + +The following options and their values are +required in the default registry to be available on the function `:math`: +- `add` + - ([digit size option](#digit-size-options)) +- `subtract` + - ([digit size option](#digit-size-options)) + +If no options or more than one option is set, +or if an _option_ value is not a [digit size option](#digit-size-options), +a _Bad Option_ error is emitted +and a _fallback value_ used as the _resolved value_ of the _expression_. + +#### Resolved Value + +The _resolved value_ of an _expression_ with a `:math` _function_ +contains the implementation-defined numeric value +of the _operand_ of the annotated _expression_. + +If the `add` option is set, +the numeric value of the _resolved value_ is formed by incrementing +the numeric value of the _operand_ by the integer value of the digit size option value. + +If the `subtract` option is set, +the numeric value of the _resolved value_ is formed by decrementing +the numeric value of the _operand_ by the integer value of the digit size option value. + +If the _operand_ of the _expression_ is an implementation-defined numeric type, +such as the _resolved value_ of an _expression_ with a `:number` or `:integer` _annotation_, +it can include option values. +These are included in the resolved option values of the _expression_. +The `:math` _options_ are not included in the resolved option values. + +> [!NOTE] +> Implementations can encounter practical limits with `:math` _expressions_, +> such as the result of adding two integers exceeding +> the storage or precision of some implementation-defined number type. +> In such cases, implementations can emit an _Unsupported Operation_ error +> or they might just silently overflow the underlying data value. + +#### Selection + +The _function_ `:math` performs selection as described in [Number Selection](#number-selection) below. + ### The `:currency` function The function `:currency` is a selector and formatter for currency values, diff --git a/test/tests/functions/math.json b/test/tests/functions/math.json new file mode 100644 index 000000000..8041e4ac3 --- /dev/null +++ b/test/tests/functions/math.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://raw.githubusercontent.com/unicode-org/message-format-wg/main/test/schemas/v0/tests.schema.json", + "scenario": "Math function", + "description": "The built-in formatter and selector for addition and subtraction.", + "defaultTestProperties": { + "bidiIsolation": "none", + "locale": "en-US" + }, + "tests": [ + { + "src": "{:math add=13}", + "expErrors": [{ "type": "bad-operand" }] + }, + { + "src": "{foo :math add=13}", + "expErrors": [{ "type": "bad-operand" }] + }, + { + "src": "{42 :math}", + "expErrors": [{ "type": "bad-option" }] + }, + { + "src": "{42 :math add=foo}", + "expErrors": [{ "type": "bad-option" }] + }, + { + "src": "{42 :math subtract=foo}", + "expErrors": [{ "type": "bad-option" }] + }, + { + "src": "{42 :math foo=13}", + "expErrors": [{ "type": "bad-option" }] + }, + { + "src": "{42 :math add=13 subtract=13}", + "expErrors": [{ "type": "bad-option" }] + }, + { + "src": "{41 :math add=1}", + "exp": "42" + }, + { + "src": "{52 :math subtract=10}", + "exp": "42" + }, + { + "src": "{41 :math add=1 foo=13}", + "exp": "42" + }, + { + "src": ".local $x = {41 :integer signDisplay=always} {{{$x :math add=1}}}", + "exp": "+42" + }, + { + "src": ".local $x = {52 :number signDisplay=always} {{{$x :math subtract=10}}}", + "exp": "+42" + }, + { + "src": "{$x :math add=1}", + "params": [{ "name": "x", "value": 41 }], + "exp": "42" + }, + { + "src": "{$x :math subtract=10}", + "params": [{ "name": "x", "value": 52 }], + "exp": "42" + }, + { + "src": ".local $x = {1 :math add=1} .match $x 1 {{=1}} 2 {{=2}} * {{other}}", + "exp": "=2" + }, + { + "src": ".local $x = {10 :integer} .local $y = {$x :math subtract=6} .match $y 10 {{=10}} 4 {{=4}} * {{other}}", + "exp": "=4" + } + ] +}