For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/2023/proposal-intl-numberformat-v3.md.
  • 简体中文
  • Intl.NumberFormat V3 S4

    提案概览
    提案速览

    该提案通过添加范围格式化、基于枚举的分组选项、新的舍入和精度控制、对十进制字符串的支持、额外的舍入模式以及新的 signDisplay 值来增强 Intl.NumberFormat。它旨在满足常见的客户端需求,并借鉴了 CLDR/ICU 中的现有实践。4、Chrome 106 和 Firefox 93 中发布。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ECMA-402 提案:Intl.NumberFormat V3

    状态:第 3 阶段(截至 2021 年 7 月

    Intl.NumberFormat 最初在最初的 Intl 规范中被添加。最近,ECMA-402 提案 Unified Intl.NumberFormat 添加了几个新的关键特性。这个我称之为“Intl.NumberFormat V3”的提案是另一批特性,这些特性已被证明对该 API 的客户端很重要。

    动机

    在 ECMA-402 中,我们每年收到数十个特性请求。在形成这个提案时,作者(sffc)考虑了所有与 Intl.NumberFormat 相关的特性请求,并将它们与以下标准进行了对比:

    1. 该特性必须有多个利益相关者。
    2. 该特性必须有健全的先前实践,例如在 CLDR、ICU 或 Unicode 中。
    3. 该特性必须难以在用户层面实现(例如依赖于区域数据)。

    本提案的所有部分都符合这一标准,而且作者的意图是,所有符合该标准的 Intl.NumberFormat 特性请求都是本提案的一部分。

    特性

    formatRange (ECMA-402 #393)

    这部分是仿照 Intl.DateTimeFormat.prototype.formatRange 提案设计的。它涉及到在 Intl.NumberFormat 原型上添加一个新函数 .formatRange(),并大体遵循 Intl.DateTimeFormat 中 .formatRange() 引入的语义。

    const nf = new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "CHF",
      maximumFractionDigits: 0,
    });
    nf.formatRange(3, 5);  // "CHF 3–5"

    还将添加对等方法:

    • Intl.NumberFormat.prototype.formatRangeToParts
    • Intl.PluralRules.prototype.selectRange (#16)

    例如:

    const pl = new Intl.PluralRules("sl");
    pl.selectRange(102, 201);  // "few"

    这里将采用 Intl.DateTimeFormat 的 formatToParts 语义:各个部分将获得一个 source 属性,其值将为 "shared""startRange""endRange"

    const nf = new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "GBP",
      currencyDisplay: "code",
      maximumFractionDigits: 0,
    });
    nf.formatRangeToParts(3, 5);
    /*
    [
      {type: "currency", value: "GBP ", source: "shared"}
      {type: "integer", value: "3", source: "startRange"}
      {type: "literal", value: "–", source: "shared"}
      {type: "integer", value: "5", source: "endRange"}
    ]
    */

    当范围的两侧在四舍五入后解析为相同的值时,将添加一个约等于符号。约等于符号会附加在数字上,并同时附加减号或加号(如适用)。(#10#11#13

    const nf = new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "EUR",
      maximumFractionDigits: 0,
    });
    nf.formatRange(2.9, 3.1);  // "~€3"
    
    const nf = new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "EUR",
      signDisplay: "always",
    });
    nf.formatRange(2.999, 3.001);  // "~+€3.00"

    如果第二个参数小于第一个参数,或者任一参数是 NaN,则抛出错误。(#12

    const nf = new Intl.NumberFormat("en-US");
    nf.formatRange(500, 1/0);  // "500–∞"
    nf.formatRange(500, 0/0);  // RangeError
    nf.formatRange(500, 0);  // RangeError
    特性检测
    if (Intl.NumberFormat.prototype.formatRange) {
      // 特性可用
    }

    分组枚举 (ECMA-402 #367)

    主要议题:#3

    目前,Intl.NumberFormat 接受一个 { useGrouping } 选项,该选项接受一个布尔值。然而,正如在错误线程中报告的,用户在指定分组时可能想要几个选项。本提案旨在使以下内容成为 { useGrouping } 的有效输入:

    • false:不显示分组分隔符
    • "min2":当一组中至少有 2 位数字时显示分组分隔符;例如,“1000”(第一组太小)和“10,000”(现在该组中至少有 2 位数字)。(Bikeshed:#23
    • "auto"(默认):根据区域偏好显示分组分隔符,这也可能取决于货币。大多数区域偏好使用分组分隔符。
    • "always":即使区域偏好不使用分组分隔符,也显示分组分隔符。
    • true"always" 的别名。
    • undefined(默认):"auto" 的别名。

    resolvedOptions 中,将返回 false 或三个字符串之一。这是一个可观察的行为变化,因为目前只返回布尔值 truefalse

    特性检测
    if (new Intl.NumberFormat("und").resolvedOptions().useGrouping === "auto") {
      // 特性可用
    }

    新的舍入/精度选项 (ECMA-402 #286)

    主要议题:#8

    附加背景:Unified NumberFormat #9

    以下附加选项被提议添加到 Intl.NumberFormat 选项对象中,以控制舍入行为:

    • roundingPriority = 一个字符串,设置为 "auto""morePrecision""lessPrecision"(详情见下文)
    • roundingIncrement = 以下列表中的数字:« 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 »
      • 镍币舍入:{ minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 5 }
      • 一角硬币舍入:{ minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 10 }
    • trailingZeroDisplay = 一个字符串,表示在整数上显示尾随零的策略:
      • "auto" = 当前行为。根据 minimumFractionDigitsminimumSignificantDigits 保留尾随零。
      • "stripIfInteger" = 与 "auto" 相同,但如果小数位均为零,则移除小数位。

    roundingIncrement 不能与有效数字舍入或除 "auto" 之外的任何 roundingPriority 设置混合使用。

    舍入优先级

    目前,Intl.NumberFormat 允许两种舍入策略:最小/最大小数位,或最小/最大有效数字。目前,如果同时指定了最小/最大小数位和最小/最大有效数字,则有效数字设置优先,小数位设置被忽略。

    新的 roundingPriority 选项指定了两种新策略来解决混合小数位和有效数字设置的问题。为了更好地表达新策略,请考虑以下选项对象:

    {
        maximumFractionDigits: 2,
        maximumSignificantDigits: 2
    }

    上述选项应被解释为:

    1. 在百分位舍入数字
    2. 在第二个有效数字之后舍入数字

    现在,考虑数字“4.321”。maximumFractionDigits 希望在百分位舍入,产生“4.32”。然而,maximumSignificantDigits 希望在两个有效数字之后舍入,产生“4.3”。因此我们存在冲突。

    新的设置 roundingPriority 提供了如何解决此冲突的提示。有三个选项:

    1. roundingPriority: "auto" 意味着有效数字在冲突中总是获胜。
    2. roundingPriority: "morePrecision" 意味着精度更高的结果在冲突中获胜。
    3. roundingPriority: "lessPrecision" 意味着精度更低的结果在冲突中获胜。

    整个结果是原子性的,包括尾随零。例如:

    {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
        minimumSignificantDigits: 2,
        maximumSignificantDigits: 6
    }

    考虑输入数字“1”。minimumFractionDigits 希望保留尾随零直到百分位,产生“1.00”,而 minimumSignificantDigits 希望只保留渲染两个有效数字所需的位数,产生“1.0”。然而,由于 maximumSignificantDigits 具有更高的精度(舍入到 10^-5,而不是小数精度的 10^-2),解析后的答案是“1.0”。

    特性检测
    if (new Intl.NumberFormat("und").resolvedOptions().roundingPriority) {
      // 特性可用
    }

    将字符串解释为十进制数 (ECMA-402 #334)

    format() 方法目前接受一个数字或一个 BigInt,而字符串被解释为数字。这部分提议将字符串重新定义为十进制数而不是数字。

    const nf = new Intl.NumberFormat("en-US");
    const string = "987654321987654321";
    nf.format(string);
    // 当前:"987,654,321,987,654,300"
    // 提议:"987,654,321,987,654,321"

    一个常见的用例是将货币金额存储为小单位(如分)的 BigInt。此 API 可用于保留 BigInt 的全部精度:

    const nf = new Intl.NumberFormat("en-US", {
        style: "currency",
        currency: "EUR",
    });
    const bi = 1000000000000000110000n;
    nf.format(bi + "E-6");
    // 当前:"€1,000,000,000,000,000.10"
    // 提议:"€1,000,000,000,000,000.11"

    十进制字符串的一般语法,基本上是 #.#E#,是计算中广泛理解的格式。我们使用的具体版本是 ECMA-262 StringNumericLiteral 语法,它允许非十进制数字,如十六进制和二进制。

    任意精度十进制字符串旨在作为格式化的直通使用。推动者并不打算让字符串成为 ECMAScript 中数字计算的事实标准。对于通用的任意精度十进制类型,请参见 Decimal 提案。

    特性检测
    if (new Intl.NumberFormat("und").format("11111111111111111112").indexOf("2") !== -1) {
      // 特性可用
    }

    舍入模式 (ECMA-402 #419)

    主要议题:#7

    Intl.NumberFormat 总是执行“四舍五入”(例如,如果你有 2.5,它会被舍入到 3)。然而,我们知道有用户和用例会受益于暴露更多舍入模式选项。

    提议的舍入模式列表是:

    1. ceil(向 +∞ 舍入)
    2. floor(向 -∞ 舍入)
    3. expand(远离 0 舍入)
    4. trunc(向 0 舍入)
    5. halfCeil(平局时向 +∞ 舍入)
    6. halfFloor(平局时向 -∞ 舍入)
    7. halfExpand(平局时远离 0 舍入;当前行为;默认值)
    8. halfTrunc(平局时向 0 舍入)
    9. halfEven(平局时向具有偶数值的值舍入)

    这些模式的行为将反映 ICU 用户指南,其中“expand”映射到 ICU 的“UP”,“trunc”映射到 ICU 的“DOWN”。

    舍入不会查看或更改数字的符号位。因此,-0 和 0 在舍入方面是等效的。(#21

    特性检测
    if (new Intl.NumberFormat("und").resolvedOptions().roundingMode) {
      // 特性可用
    }

    负数符号显示

    主要议题:#17

    根据客户的反馈,提议了一个新选项 signDisplay: "negative"。新选项的行为将类似于 "auto",只是符号不会显示在负零上。

    var nf = new Intl.NumberFormat("en", {
      signDisplay: "negative"
    });
    nf.format(-1.0);  // -1
    nf.format(-0.0);  // 0  (注意:“auto”在此产生“-0”)
    nf.format(0.0);   // 0
    nf.format(1.0);   // 1  (注意:“exceptZero”在此产生“+1”)
    特性检测
    try {
      new Intl.NumberFormat("und", { signDisplay: "negative" });
      // 特性可用
    } catch(e) {
      // 特性不可用
    }

    实现状态

    已在以下环境中发布:

    • Safari 15.4
    • Chrome 106
    • Firefox 93
    最新规范文本的 V8 原型可以在 https://chromium-review.googlesource.com/c/v8/v8/+/2336146 找到,使用标记 --harmony_intl_number_format_v3