Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add :percent #988

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions spec/functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1. [`:integer`](number.md#the-integer-function)
1. [`:math`](number.md#the-math-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)
Expand Down
148 changes: 111 additions & 37 deletions spec/functions/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ The following options and their values are required to be available on the funct
- `exceptZero`
- `negative`
- `never`
- `style`
- `decimal` (default)
- `percent` (see [Percent Style](#percent-style) below)
- `useGrouping`
- `auto` (default)
- `always`
Expand Down Expand Up @@ -120,21 +117,6 @@ but can cause problems in target locales that the original developer is not cons
> The `one` variant is needed by languages such as Polish or Russian.
> Such locales typically also require other keywords such as `two`, `few`, and `many`.

##### Percent Style

When implementing `style=percent`, the numeric value of the _operand_
MUST be multiplied by 100 for the purposes of formatting.

> For example,
>
> ```
> The total was {0.5 :number style=percent}.
> ```
>
> should format in a manner similar to:
>
> > The total was 50%.

#### Resolved Value

The _resolved value_ of an _expression_ with a `:number` _function_
Expand Down Expand Up @@ -183,9 +165,6 @@ function `:integer`:
- `exceptZero`
- `negative`
- `never`
- `style`
- `decimal` (default)
- `percent` (see [Percent Style](#percent-style) below)
- `useGrouping`
- `auto` (default)
- `always`
Expand Down Expand Up @@ -232,21 +211,6 @@ but can cause problems in target locales that the original developer is not cons
> The `one` variant is needed by languages such as Polish or Russian.
> Such locales typically also require other keywords such as `two`, `few`, and `many`.

##### Percent Style

When implementing `style=percent`, the numeric value of the _operand_
MUST be multiplied by 100 for the purposes of formatting.

> For example,
>
> ```
> The total was {0.5 :number style=percent}.
> ```
>
> should format in a manner similar to:
>
> > The total was 50%.

#### Resolved Value

The _resolved value_ of an _expression_ with an `:integer` _function_
Expand Down Expand Up @@ -526,6 +490,117 @@ together with the resolved options' values.

The _function_ `:currency` performs selection as described in [Number Selection](#number-selection) below.

### The `:percent ` function

The function `:percent` is a formatter for percentage values,
which are a specialized form of numeric formatting.

#### Operands

The function `:percent` requires a [Number Operand](#number-operands) as its _operand_.

For the purposes of formatting,
the numeric value of the _operand_ is multiplied by 100.
Comment on lines +502 to +503
Copy link
Member

Choose a reason for hiding this comment

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

Since we have a separate function, purpose built for it, should we offer a pre-computed mode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You mean an option toggling if the input is multiplied or not?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, exactly.

Copy link
Member

Choose a reason for hiding this comment

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

ICU4C calls it "scale" or "multiplier"

ICU4X calls it "multiply_pow10"

I don't think we've had a proper bikeshedding session on this before. I've sort-of named it what I felt like naming it and no one ever pushed back with better names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Given that we'll be defaulting to a value 1 getting formatted as something like "100%", I think scale makes the most sense, with a default value of 1 and supporting 100 as an alternative. With this approach,

{42 :percent scale=100}

would then be formatted as "42%".

Or is the ICU4C meaning of this option the exact opposite?

Copy link
Member

Choose a reason for hiding this comment

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

Another thought: an enumeration might be appropriate here. Something like:

  1. scalingMode: "scale" scales the number: 0.5 => 50%
  2. scalingMode: "passThrough" passes through the number: 50 => 50%

(sorry for the quad-post)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In that case, scale is a rather sub-optimal choice, as it demonstrably can be understood as either "scale the number by this much" or "the scale extends to this value as the maximum".

Copy link
Member

Choose a reason for hiding this comment

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

More suggestions, with some help from an LLM

enum PercentageScale { Fraction, Percentage }
enum InputMode { Decimal, Scaled }
enum ValueType { Normalized, Preformatted }
enum PercentMode { Ratio, Percent }
enum ScaleType { UnitInterval, WholePercent }
enum InputScale { PartsPerHundred, FinalPercent }
enum Scale { Raw, Scaled }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe style=raw, as an alternative to a default style=scaled?

Copy link
Member

Choose a reason for hiding this comment

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

I would like to avoid any option with the unqualified style name.


> For example,
>
> ```
> The total was {0.5 :percent}.
> ```
>
> should format in a manner similar to:
>
> > The total was 50%.

#### 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 _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 and their values are required to be available on the function `:number`:

- `numberingSystem`
- valid [Unicode Number System Identifier](https://cldr-smoke.unicode.org/spec/main/ldml/tr35.html#UnicodeNumberSystemIdentifier)
(default is locale-specific)
- `signDisplay`
- `auto` (default)
- `always`
- `exceptZero`
- `negative`
- `never`
- `useGrouping`
- `auto` (default)
- `always`
- `never`
- `min2`
- `minimumIntegerDigits`
- ([digit size option](#digit-size-options), default: `1`)
- `minimumFractionDigits`
- ([digit size option](#digit-size-options))
- `maximumFractionDigits`
- ([digit size option](#digit-size-options))
- `minimumSignificantDigits`
- ([digit size option](#digit-size-options))
- `maximumSignificantDigits`
- ([digit size option](#digit-size-options))
- `trailingZeroDisplay`
- `auto` (default)
- `stripIfInteger`
- `roundingPriority`
- `auto` (default)
- `morePrecision`
- `lessPrecision`
- `roundingIncrement`
- 1 (default), 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, and 5000
- `roundingMode`
- `ceil`
- `floor`
- `expand`
- `trunc`
- `halfCeil`
- `halfFloor`
- `halfExpand` (default)
- `halfTrunc`
- `halfEven`

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 option values of the _operand_.
Option values with the following names are however discarded if included in the _operand_:

- `compactDisplay`
- `notation`
- `select`
Comment on lines +578 to +582
Copy link
Member

Choose a reason for hiding this comment

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

Just say that all options not named in the :percent function's list above are discarded?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That would be different from what we do with other functions, such as :integer, and it would make extensibility difficult.

An implementation could support additional options that are set in the calling code on the value being formatted, and are understood by the formatter. For a somewhat silly example, an implementation could support colour as a property of numbers, and format a "green 0.42" so that the % sign was also green in the output. If we here said that all options not explicitly listed are discarded, this kind of extensibility would not be possible.


> For example, the _placeholder_ in this _message_:
>
> ```
> .input {$n :number minimumFractionDigits=2 roundingMode=floor}
> {{{$n :percent minimumFractionDigits=1}}}
> ```
>
> would be formatted with the resolved options
> `{ minimumFractionDigits: '1', roundingMode: 'floor' }`.

#### 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_ multiplied by 100,
together with the resolved options' values.
Copy link
Member

Choose a reason for hiding this comment

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

For clarity, and since "resolved value" and "options" are the terms, perhaps:

Suggested change
together with the resolved options' values.
together with the _resolved values_ of the _options_.

Copy link
Collaborator Author

@eemeli eemeli Jan 22, 2025

Choose a reason for hiding this comment

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

Variants of this same snippet are used in nearly all the functions. This change would be better made separately, affecting all of them at once.

But yes, referring to the options' resolved values would be appropriate, but note that this isn't the same as options, given that the resolved options are determined by the function handler. In practice they can include ones coming from the operand as well, and don't necessarily contain all of the options.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps "resolved values of the resolved list of options"? Oh the pedantry...


To avoid potential confusion from the multiplication of the _operand_ value,
this _resolved value_ MUST NOT be considered a valid [Number Operand](#number-operands).

### The `:unit` function

The _function_ `:unit` is **Proposed** for inclusion in the next release of this specification but has not yet been finalized.
Expand Down Expand Up @@ -863,7 +938,6 @@ representing its decimal value:
- `minimumSignificantDigits`
- `maximumSignificantDigits`
- `notation`
- `style`

```abnf
integer = "0" / ["-"] ("1"-"9") *DIGIT
Expand Down