Intl.NumberFormat API
使用 JavaScript 内置的国际化 API 为任何语言环境格式化数字
介绍
在您的应用程序中显示数字 1234567.89 时,使用 toString() 会生成 "1234567.89",这既难以阅读,又假设所有人都使用小数点并从左到右阅读数字。美国人期望看到 "1,234,567.89",德国人期望看到 "1.234.567,89",而印度用户期望看到 "12,34,567.89",每个地区的分组规则都不同。
Intl.NumberFormat API 通过根据特定语言环境的约定格式化数字来解决这个问题。它处理千位分隔符、小数点、数字分组、货币符号、百分比符号、计量单位和数字系统。这消除了手动字符串操作或使用第三方库的需求。
本指南将解释如何使用 Intl.NumberFormat 为全球用户正确格式化数字,从基本用法开始,逐步介绍货币格式化、紧凑表示法和自定义舍入模式等高级功能。
使用默认设置格式化数字
通过调用 new Intl.NumberFormat() 并传入语言环境字符串来创建格式化器,然后使用数字调用其 format() 方法。
const formatter = new Intl.NumberFormat('en-US');
formatter.format(1234567.89);
// "1,234,567.89"
格式化器根据语言环境添加千位分隔符并格式化小数点。如果未指定语言环境,格式化器将使用运行时的默认语言环境,通常基于用户的系统设置。
const formatter = new Intl.NumberFormat('de-DE');
formatter.format(1234567.89);
// "1.234.567,89"
德国的约定使用点号作为千位分隔符,逗号作为小数点,与美国的约定相反。格式化器会自动处理这些差异。
了解语言环境代码
语言环境代码标识一种语言,并可选地标识一个地区,格式为 language-REGION。语言使用两个字母的 ISO 639-1 代码,例如 en 或 es。地区使用两个字母的 ISO 3166-1 代码,例如 US 或 MX。
new Intl.NumberFormat('en-US').format(1234.56);
// "1,234.56"(美式英语)
new Intl.NumberFormat('en-GB').format(1234.56);
// "1,234.56"(英式英语)
new Intl.NumberFormat('es-ES').format(1234.56);
// "1234,56"(欧洲西班牙语)
new Intl.NumberFormat('es-MX').format(1234.56);
// "1,234.56"(墨西哥西班牙语)
两种英语变体使用相同的格式,但西班牙语变体有所不同。欧洲西班牙语对四位数省略千位分隔符,并使用逗号作为小数点,而墨西哥西班牙语遵循美国的约定。
根据用户的位置或语言偏好选择语言环境。应用程序通常通过用户设置、浏览器语言或 IP 地理定位来确定语言环境。
选择格式化样式
style 选项决定了格式化的类别。将一个选项对象作为第二个参数传递给构造函数。
new Intl.NumberFormat('en-US', {
style: 'decimal'
}).format(1234.56);
// "1,234.56"
decimal 样式是默认值。其他样式包括 currency、percent 和 unit。
使用符号和代码格式化货币
currency 样式需要一个包含 ISO 4217 货币代码的 currency 选项。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1234.56);
// "$1,234.56"
格式化器会添加美元符号,并默认格式化为两位小数,这是大多数货币的标准。不同的区域会以不同的方式放置符号。
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
// "1.234,56 €"
德语格式会在金额后加上欧元符号,并留有一个空格。currency 选项决定显示哪种货币,而不是遵循哪个区域的惯例。区域决定了格式化的惯例,而货币决定了符号。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
// "€1,234.56"
美国的格式化惯例与欧元符号结合,会在金额前显示欧元符号,遵循美国而非欧洲的放置惯例。
控制货币显示格式
currencyDisplay 选项可以更改货币在格式化字符串中的显示方式。
const amount = 1234.56;
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol'
}).format(amount);
// "$1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'code'
}).format(amount);
// "USD 1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'name'
}).format(amount);
// "1,234.56 US dollars"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: 'narrowSymbol'
}).format(amount);
// "$1,234.56"
symbol 选项是默认值,显示货币符号如 $ 或 €。code 选项显示三字母货币代码。name 选项拼写出货币名称。narrowSymbol 选项使用区域的窄货币符号,可能会有歧义,但节省空间。
symbol 和 narrowSymbol 的区别在于共享符号的货币。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'CAD',
currencyDisplay: 'symbol'
}).format(100);
// "CA$100.00"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'CAD',
currencyDisplay: 'narrowSymbol'
}).format(100);
// "$100.00"
使用 symbol 选项时,加拿大元显示为 CA$,以区分美元;而使用 narrowSymbol 时,仅显示为 $。
使用会计符号格式化负货币金额
currencySign 选项控制负金额的显示方式。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'standard'
}).format(-1234.56);
// "-$1,234.56"
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'accounting'
}).format(-1234.56);
// "($1,234.56)"
standard 选项是默认值,使用减号表示负数。accounting 选项将负金额用括号括起来,符合会计惯例。这使得负数在财务报告中更加醒目。
格式化百分比
percent 样式将数字乘以 100 并添加百分号。
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.1234);
// "12%"
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.1256);
// "13%"
格式化器默认四舍五入到最接近的整数。可以通过数字选项控制小数位数。
new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2
}).format(0.1234);
// "12.34%"
传递百分比的小数形式,而不是已乘以 100 的形式。格式化器会自动处理乘法。
使用单位格式化测量值
unit 样式需要一个 unit 选项来指定单位标识符。
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer-per-hour'
}).format(100);
// "100 km/h"
new Intl.NumberFormat('en-GB', {
style: 'unit',
unit: 'mile-per-hour'
}).format(100);
// "100 mph"
可用的单位包括长度测量(meter、kilometer、mile)、时间持续(second、minute、hour)、数字存储(byte、kilobyte、megabyte)、温度(celsius、fahrenheit)等。复合单位如 kilometer-per-hour 使用连字符连接简单单位。
unitDisplay 选项控制单位的显示格式。
const distance = 1234.5;
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'long'
}).format(distance);
// "1,234.5 公里"
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'short'
}).format(distance);
// "1,234.5 km"
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'kilometer',
unitDisplay: 'narrow'
}).format(distance);
// "1,234.5km"
long 选项会拼写出单位名称。short 选项使用缩写形式。narrow 选项使用最紧凑的形式,但可能会有歧义。
使用紧凑表示法显示大数字
notation 选项可以改变数字的表示方式。compact 值使用特定于语言环境的简写形式来表示大数字。
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).format(1234567);
// "1.2M"
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).format(987654321);
// "988M"
紧凑表示法默认保留一位小数,并添加诸如 K(千)、M(百万)和 B(十亿)等后缀。这种格式常用于社交媒体的关注者数量、视频观看次数和分析仪表板。
compactDisplay 选项可以控制后缀的长度。
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short'
}).format(1234567);
// "1.2M"
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'long'
}).format(1234567);
// "1.2 million"
short 选项是默认值,使用符号表示。long 选项则拼写出数量级的单词。不同的语言环境使用不同的后缀。
new Intl.NumberFormat('zh-CN', {
notation: 'compact'
}).format(123456789);
// "1.2亿"
中文使用 亿 表示一亿,反映了语言的数字分组系统。
用科学计数法表示非常大的或非常小的数字
scientific 表示法将数字表示为系数乘以十的幂。
new Intl.NumberFormat('en-US', {
notation: 'scientific'
}).format(123456789);
// "1.235E8"
new Intl.NumberFormat('en-US', {
notation: 'scientific'
}).format(0.00000123);
// "1.23E-6"
这种格式适用于非常大的数字(如天文距离、分子数量)和非常小的数字(如粒子质量、纳米级测量)。指数始终以 1 的倍数出现。
在技术应用中使用工程计数法
engineering 表示法类似于科学计数法,但将指数限制为 3 的倍数。
new Intl.NumberFormat('en-US', {
notation: 'engineering'
}).format(123456789);
// "123.457E6"
new Intl.NumberFormat('en-US', {
notation: 'engineering'
}).format(1234);
// "1.234E3"
工程计数法与国际单位制(SI)前缀(如千、兆、吉)对齐,因此在工程和物理学领域中是标准格式。系数范围为 1 到 999。
使用小数位数控制小数点
minimumFractionDigits 和 maximumFractionDigits 选项控制小数点后显示的位数。
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234.5);
// "1,234.50"
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(1234.567);
// "1,234.57"
minimumFractionDigits 确保在需要时显示尾随零。maximumFractionDigits 则会对较长的小数进行四舍五入。货币格式化器默认使用两位小数。小数格式化器默认的最小位数为零,最大位数为三。
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(1234.567);
// "1,235"
将两者都设置为零时,会四舍五入到最接近的整数。
使用有效数字控制整体精度
minimumSignificantDigits 和 maximumSignificantDigits 选项控制总精度,而不考虑小数点的位置。
new Intl.NumberFormat('en-US', {
minimumSignificantDigits: 3,
maximumSignificantDigits: 3
}).format(1234.567);
// "1,230"
new Intl.NumberFormat('en-US', {
minimumSignificantDigits: 3,
maximumSignificantDigits: 3
}).format(0.001234);
// "0.00123"
有效数字包括除前导零以外的所有数字。第一个示例将数字四舍五入到三位,结果为 1230。第二个示例保留了前导零后的三位数字,结果为 0.00123。
当同时指定有效数字选项和小数位数选项时,有效数字选项会覆盖小数位数选项。
使用舍入模式选择舍入策略
roundingMode 选项决定在需要截断时如何进行舍入。
const value = 1.5;
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfExpand'
}).format(value);
// "2"
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfTrunc'
}).format(value);
// "1"
halfExpand 模式是默认模式,它将 0.5 向远离零的方向舍入,这是学校中常见的舍入策略。而 halfTrunc 模式则将 0.5 向零的方向舍入。
其他模式包括:
ceil:始终向正无穷方向舍入floor:始终向负无穷方向舍入expand:始终向远离零的方向舍入trunc:始终向零的方向舍入halfCeil:将 0.5 向正无穷方向舍入halfFloor:将 0.5 向负无穷方向舍入halfEven:将 0.5 向最接近的偶数舍入
const prices = [1.5, 2.5, 3.5];
prices.map(price =>
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 0,
roundingMode: 'halfEven'
}).format(price)
);
// ["2", "2", "4"]
halfEven 模式,也称为银行家舍入,减少了重复计算中的舍入偏差。当舍入 0.5 时,它选择最接近的偶数。这使得 1.5 和 2.5 的结果为 2,而 3.5 的结果为 4。
金融应用中使用 ceil 来向上舍入费用,使用 floor 来向下舍入退款。统计应用中使用 halfEven 来最小化累积舍入误差。
使用分组选项控制千位分隔符
useGrouping 选项控制是否显示千位分隔符。
new Intl.NumberFormat('en-US', {
useGrouping: true
}).format(123456);
// "123,456"
new Intl.NumberFormat('en-US', {
useGrouping: false
}).format(123456);
// "123456"
true 是默认值。false 值会移除所有分隔符。字符串值提供更精细的控制。
new Intl.NumberFormat('en-US', {
useGrouping: 'always'
}).format(1234);
// "1,234"
new Intl.NumberFormat('en-US', {
useGrouping: 'min2'
}).format(1234);
// "1234"
always 值在所有情况下都使用分隔符。min2 值会省略四位数字的分隔符。auto 值遵循区域设置的偏好,通常与 min2 的行为一致。
紧凑格式默认使用 min2,因为紧凑数字很少需要内部分隔符。
使用符号显示选项显式显示符号
signDisplay 选项控制正负号的显示时机。
new Intl.NumberFormat('en-US', {
signDisplay: 'auto'
}).format(100);
// "100"
new Intl.NumberFormat('en-US', {
signDisplay: 'always'
}).format(100);
// "+100"
auto 是默认值,负数显示减号,正数不显示加号。always 值会同时显示正负号。
new Intl.NumberFormat('en-US', {
signDisplay: 'exceptZero'
}).format(0);
// "0"
new Intl.NumberFormat('en-US', {
signDisplay: 'always'
}).format(0);
// "+0"
exceptZero 值即使在 always 行为下也会省略零值的符号。这可以避免显示令人困惑的 +0 和 -0。
new Intl.NumberFormat('en-US', {
signDisplay: 'never'
}).format(-100);
// "100"
never 值完全移除符号,仅显示绝对值。
金融应用使用 always 来通过加号突出显示收益。温度显示使用 exceptZero 来避免显示 +0° 或 -0°。
将格式化输出分解为部分
formatToParts() 方法返回一个对象数组,每个对象表示格式化字符串的一个组成部分。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatToParts(1234.56);
返回结果:
[
{ type: 'currency', value: '$' },
{ type: 'integer', value: '1' },
{ type: 'group', value: ',' },
{ type: 'integer', value: '234' },
{ type: 'decimal', value: '.' },
{ type: 'fraction', value: '56' }
]
每个对象包含一个 type 属性,用于标识组成部分,以及一个 value 属性,包含字符串值。type 的值可以是 currency、integer、group、decimal、fraction、literal、minusSign、plusSign、percentSign 等。
这使得可以对各个组成部分进行自定义样式。
const parts = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatToParts(1234.56);
const formatted = parts.map(part => {
if (part.type === 'currency') {
return `<span class="currency">${part.value}</span>`;
}
if (part.type === 'integer') {
return `<span class="integer">${part.value}</span>`;
}
return part.value;
}).join('');
// <span class="currency">$</span><span class="integer">1</span>,<span class="integer">234</span>.56
这将生成带有样式的 HTML 组件。同样的方法可以用于为负数金额应用不同的颜色、放大货币符号或为单个数字添加动画效果。
格式化数字范围
formatRange() 方法将两个数字格式化为一个范围。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatRange(100, 200);
// "$100.00 – $200.00"
格式化器使用特定于语言环境的范围分隔符(在英语中是短横线)并包含两个货币符号。如果两个值格式化为相同的字符串,格式化器将返回带有波浪号的单个值。
new Intl.NumberFormat('en-US', {
notation: 'compact'
}).formatRange(1200, 1800);
// "~1K"
在紧凑表示法中,1200 和 1800 都格式化为 1K,因此格式化器显示大约为 1K。
formatRangeToParts() 方法返回范围的组成部分。
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).formatRangeToParts(100, 200);
返回结果:
[
{ type: 'currency', value: '$', source: 'startRange' },
{ type: 'integer', value: '100', source: 'startRange' },
{ type: 'decimal', value: '.', source: 'startRange' },
{ type: 'fraction', value: '00', source: 'startRange' },
{ type: 'literal', value: ' – ', source: 'shared' },
{ type: 'currency', value: '$', source: 'endRange' },
{ type: 'integer', value: '200', source: 'endRange' },
{ type: 'decimal', value: '.', source: 'endRange' },
{ type: 'fraction', value: '00', source: 'endRange' }
]
source 属性标识组成部分是来自起始值、结束值还是范围分隔符。
价格范围、日期范围和测量范围使用此方法进行适当的语言环境敏感格式化。
检查解析后的选项
resolvedOptions() 方法返回一个对象,显示格式化器实际使用的选项。
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
formatter.resolvedOptions();
返回结果如下:
{
locale: 'en-US',
numberingSystem: 'latn',
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol',
currencySign: 'standard',
minimumIntegerDigits: 1,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: 'auto',
notation: 'standard',
signDisplay: 'auto',
roundingMode: 'halfExpand'
}
该对象包括显式设置的选项和默认值。如果系统解析到一个不同但兼容的区域设置,locale 可能与请求的区域设置不同。numberingSystem 显示格式化器使用的数字字符。
此方法通过显示所有活动设置,有助于调试意外的格式化行为。
重用格式化器以提高性能
创建一个 NumberFormat 实例需要加载区域设置数据和处理选项。当使用相同设置格式化多个值时,请重用实例。
// 效率低:每个值都创建新的格式化器
prices.map(price =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price)
);
// 高效:只创建一次格式化器
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
prices.map(price => formatter.format(price));
第二种方法在格式化多个值时显著更快。在循环和组件渲染函数外部创建格式化器。
现代 JavaScript 引擎会在内部缓存 NumberFormat 实例,但显式重用可以提供更好的性能和更清晰的代码。
检查浏览器支持
Intl.NumberFormat API 在所有现代浏览器中都受支持。自 2016 年以来,Chrome、Firefox、Safari 和 Edge 就支持基本的 API。像 formatRange()、formatRangeToParts() 和扩展的 roundingMode 选项等高级功能最近才获得支持,但现在已在当前浏览器版本中可用。
可以使用以下方法检查支持情况:
if (typeof Intl !== 'undefined' && Intl.NumberFormat) {
// 支持 NumberFormat
const formatter = new Intl.NumberFormat('en-US');
}
检查特定功能:
const formatter = new Intl.NumberFormat('en-US');
if (typeof formatter.formatRange === 'function') {
// 支持 formatRange
}
需要支持旧版浏览器的应用程序可以使用 @formatjs/intl-numberformat 等 polyfill,但大多数现代应用程序可以直接使用原生 API 而无需回退。
何时使用 NumberFormat
使用 Intl.NumberFormat 的场景:
- 在任何用户界面上下文中向用户显示数字
- 在电子商务应用中格式化货币金额
- 在分析仪表板中显示百分比
- 显示关注者数量、观看次数和其他社交指标
- 格式化测量值和科学数值
- 构建支持多种语言环境的国际化应用程序
不要将 Intl.NumberFormat 用于:
- 内部计算或数据处理
- 在数据库中存储值
- 为 API 序列化数据
- 将用户输入解析回数字
NumberFormat 是一个展示层工具。在计算和存储中保留原始数值,仅在向用户显示时应用格式化。