diff --git a/exploration/percent-format.md b/exploration/percent-format.md index 4dc958a816..49930b4f39 100644 --- a/exploration/percent-format.md +++ b/exploration/percent-format.md @@ -1,6 +1,6 @@ # Formatting Percent Values -Status: **Proposed** +Status: **Accepted**
Metadata @@ -11,7 +11,8 @@ Status: **Proposed**
First proposed
2025-04-07
Pull Requests
-
#1068
+
#1068
+
#1094
@@ -134,9 +135,12 @@ _What prior decisions and existing conditions limit the possible design?_ ## Proposed Design -_Describe the proposed solution. Consider syntax, formatting, errors, registry, tooling, interchange._ +Add a new, dedicated `:percent` function, +which scales the _resolved value_ of its _operand_ by 100. -TBD +This design could allow a function such as `:unit unit=percent` +for formatting values without scaling +when the working group tackles units. ## Alternatives Considered diff --git a/spec/functions/README.md b/spec/functions/README.md index f250306f95..126a8606cb 100644 --- a/spec/functions/README.md +++ b/spec/functions/README.md @@ -9,6 +9,7 @@ 1. [`:integer`](number.md#the-integer-function) 1. [`:offset`](number.md#the-offset-function) 1. [`:currency`](number.md#the-currency-function) + 1. [`:percent`](number.md#the-percent-function) 1. [`:unit`](number.md#the-unit-function) 1. [Date and Time Value Formatting](datetime.md) 1. [`:datetime`](datetime.md#the-datetime-function) diff --git a/spec/functions/number.md b/spec/functions/number.md index e2a7404e02..4504d572f9 100644 --- a/spec/functions/number.md +++ b/spec/functions/number.md @@ -413,6 +413,124 @@ contains an implementation-defined currency value of the _operand_ of the annotated _expression_, together with the resolved options' values. +#### The `:percent` function + +> [!IMPORTANT] +> The _function_ `:percent` has a status of **Draft**. +> It is proposed for inclusion in a future release of this specification and is not Stable. + +The function `:percent` is a selector and formatter for percent values. + +##### `:percent` Operands + +The function `:percent` requires a _numeric operand_ as its _operand_. + +When either selecting or formatting the _expression_, +the numeric value of the _operand_ is multiplied by 100. + +##### `:percent` Options + +Some options do not have default values defined in this specification. +The defaults for these options are implementation-dependent. +In general, the default values for such options depend on the locale, +the value of other options, or both. + +> [!NOTE] +> The names of _options_ and their _option values_ were derived from the +> [options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options) +> in JavaScript's `Intl.NumberFormat`. + +The following _options_ are REQUIRED to be available on the function `:percent`: + +- `signDisplay` + - `auto` (default) + - `always` + - `exceptZero` + - `negative` + - `never` +- `useGrouping` + - `auto` (default) + - `always` + - `never` + - `min2` +- `minimumFractionDigits` + - _digit size option_, default: `0` +- `maximumFractionDigits` + - _digit size option_, default: `0` +- `minimumSignificantDigits` + - _digit size option_ +- `maximumSignificantDigits` + - _digit size option_ +- `trailingZeroDisplay` + - `auto` (default) + - `stripIfInteger` +- `roundingPriority` + - `auto` (default) + - `morePrecision` + - `lessPrecision` +- `roundingMode` + - `ceil` + - `floor` + - `expand` + - `trunc` + - `halfCeil` + - `halfFloor` + - `halfExpand` (default) + - `halfTrunc` + - `halfEven` + +The numeric value of the _operand_ is multiplied by 100 +at the start of formatting or selection. +Each _option_ is applied to the formatted (or selected) value +rather than the unaltered value of the _operand_. + +> For example, this _placeholder_: +> +> ``` +> {0.1234 :percent maximumFractionDigits=1} +> ``` +> +> might be formatted as "12.3%" in an English locale. + +If the _operand_ of the _expression_ is an implementation-defined type, +such as the _resolved value_ of an _expression_ with a `:number` or `:integer` _annotation_, +it can include option values. +In general, these are included in the resolved option values of the _expression_, +with _options_ on the _expression_ taking priority over any options of the _operand_. +Options with the following names are however discarded if included in the _operand_: + +- `minimumIntegerDigits` +- `roundingIncrement` +- `select` + +##### `:percent` Resolved Value + +The _resolved value_ of an _expression_ with a `:percent` _function_ +contains an implementation-defined numerical value +of the _operand_ of the annotated _expression_ +together with the resolved options' values. +The numerical value of the _resolved value_ of the _expression_ +is the same as the numerical value of its _operand_; +it is not multiplied by 100. + +##### Selection with `:percent` + +The _function_ `:percent` performs selection as described in [Number Selection](#number-selection) below. +This selection always uses the `plural` selection mode, +and is performed on the numerical value of the _operand_ +multiplied by 100. + +> For example, this _message_: +> ``` +> .local $pct = {1 :percent} +> .match $pct +> 1 {{Would match with 0.01 as the operand}} +> 100 {{Matches 💯}} +> * {{Otherwise}} +> ``` +> +> would be formatted as "Matches 💯". + #### The `:unit` function > [!IMPORTANT] diff --git a/test/README.md b/test/README.md index f936abf59f..352795f964 100644 --- a/test/README.md +++ b/test/README.md @@ -12,6 +12,7 @@ These test files are intended to be useful for testing multiple different _messa > If your implementation uses UTF-16 based strings (such as JavaScript `String` or Java `java.lang.String`) > or otherwise allows unpaired surrogates in text or literals, you will need to implement tests equivalent > to the following for syntax errors: +> > ```json > { > "locale": "en-US", @@ -72,11 +73,12 @@ Tests for such features have a `tags` array attached to them to mark the features that they rely on. This may include one or more of the following: -| Tag | Feature | -| ---------- | ----------------------------------------------------- | -| `u:dir` | The [u:dir](../spec/u-namespace.md#udir) option | -| `u:id` | The [u:id](../spec/u-namespace.md#uid) option | -| `u:locale` | The [u:locale](../spec/u-namespace.md#ulocale) option | +| Tag | Feature | +| ---------- | ------------------------------------------------------------------------- | +| `:percent` | The [:percent](../spec/functions/number.md#the-percent-function) function | +| `u:dir` | The [u:dir](../spec/u-namespace.md#udir) option | +| `u:id` | The [u:id](../spec/u-namespace.md#uid) option | +| `u:locale` | The [u:locale](../spec/u-namespace.md#ulocale) option | ## Test Functions @@ -199,8 +201,9 @@ emit a _Bad Option_ error. > Actual functions in your implementation might emit > an implementation-defined or platform-specific runtime error or exception > when the function handler is called. -> Your implementation might thus produce a _Message Function Error_ +> Your implementation might thus produce a _Message Function Error_ > not provided with a label in the JSON Schema of this test suite. + ### `:test:select` This _function_ accepts the same _operands_ and _options_, diff --git a/test/schemas/v0/tests.schema.json b/test/schemas/v0/tests.schema.json index d0215dae33..11a0d8ca2f 100644 --- a/test/schemas/v0/tests.schema.json +++ b/test/schemas/v0/tests.schema.json @@ -204,6 +204,7 @@ "type": "array", "items": { "enum": [ + ":percent", "u:dir", "u:id", "u:locale" diff --git a/test/tests/functions/percent.json b/test/tests/functions/percent.json new file mode 100644 index 0000000000..69b91d7c98 --- /dev/null +++ b/test/tests/functions/percent.json @@ -0,0 +1,45 @@ +{ + "$schema": "../../schemas/v0/tests.schema.json", + "scenario": "Percent function", + "description": "The built-in formatter and selector for percent values.", + "defaultTestProperties": { + "tags": [":percent"], + "bidiIsolation": "none", + "locale": "en-US", + "expErrors": [] + }, + "tests": [ + { + "src": "{:percent}", + "expErrors": [{ "type": "bad-operand" }], + "exp": "{:percent}" + }, + { + "src": "{foo :percent}", + "expErrors": [{ "type": "bad-operand" }], + "exp": "{|foo|}" + }, + { "src": "{1 :percent}" }, + { "src": ".local $n = {0.42 :number} {{{$n :percent}}}" }, + { "src": ".local $n = {42 :integer} {{{$n :percent}}}" }, + { "src": ".local $n = {0.01 :percent} {{{$n :percent}}}" }, + { "src": "{0.12345678 :percent}" }, + { "src": "{0.12345678 :percent maximumFractionDigits=1}" }, + { "src": "{0.12 :percent minimumFractionDigits=1}" }, + { "src": "{0.12 :percent minimumSignificantDigits=1}" }, + { + "src": "{$x :percent}", + "params": [{ "name": "x", "value": 0.99 }] + }, + { + "src": ".input {$n :percent} .match $n one {{one}} * {{other}}", + "params": [{ "name": "n", "value": 0.01 }], + "exp": "one" + }, + { + "src": ".input {$n :percent} .match $n one {{one}} * {{other}}", + "params": [{ "name": "n", "value": 1 }], + "exp": "other" + } + ] +}