跳到主要内容

千分位格式化数字

问题

实现一个函数,把数字转为千分位格式的字符串。

示例:

输入:1234567.89
输出:"1,234,567.89"

输入:-1234567
输出:"-1,234,567"

输入:1000
输出:"1,000"
使用场景

金额展示、数据大盘、统计报表等几乎所有前端项目都需要数字格式化。这道题简单但非常实用。

答案

方法一:toLocaleString(推荐,实际开发用这个)

formatNumber.ts
function formatNumber(num: number): string {
return num.toLocaleString('en-US');
}

// 测试
formatNumber(1234567.89); // "1,234,567.89"
formatNumber(-1234567); // "-1,234,567"
formatNumber(0.123); // "0.123"
扩展功能
// 控制小数位数
(1234567.8).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}); // "1,234,567.80"

// 货币格式
(1234567).toLocaleString('zh-CN', {
style: 'currency',
currency: 'CNY',
}); // "¥1,234,567.00"

// Intl.NumberFormat(更灵活)
new Intl.NumberFormat('en-US').format(1234567.89);

方法二:正则表达式(面试必会)

formatNumberRegex.ts
function formatNumber(num: number): string {
const [intPart, decPart] = String(num).split('.');

// 正则:匹配后面跟着 3n 个数字的位置
const formatted = intPart.replace(/\B(?=(\d{3})+$)/g, ',');

return decPart !== undefined ? `${formatted}.${decPart}` : formatted;
}

正则解析

/\B(?=(\d{3})+$)/g

\B 非单词边界(避免在开头插入逗号)
(?= 正向前瞻
(\d{3})+ 匹配 3 的倍数个数字
$ 直到字符串结尾
)
"1234567" 中匹配的位置:
1 | 2 3 4 | 5 6 7 → "1,234,567"
^ 这里 ^ 这里

方法三:从右到左遍历(手写循环)

formatNumberLoop.ts
function formatNumber(num: number): string {
const str = String(num);
const [intPart, decPart] = str.split('.');

// 处理负号
const isNegative = intPart.startsWith('-');
const digits = isNegative ? intPart.slice(1) : intPart;

const result: string[] = [];
let count = 0;

// 从右到左遍历
for (let i = digits.length - 1; i >= 0; i--) {
result.unshift(digits[i]);
count++;
if (count % 3 === 0 && i > 0) {
result.unshift(',');
}
}

let formatted = result.join('');
if (isNegative) formatted = '-' + formatted;
if (decPart !== undefined) formatted += '.' + decPart;

return formatted;
}

方法四:reduce

formatNumberReduce.ts
function formatNumber(num: number): string {
const [intPart, decPart] = String(num).split('.');
const isNegative = intPart.startsWith('-');
const digits = isNegative ? intPart.slice(1) : intPart;

const formatted = digits
.split('')
.reverse()
.reduce((acc, digit, i) => {
return (i > 0 && i % 3 === 0 ? digit + ',' : digit) + acc;
}, '');

return (isNegative ? '-' : '') + formatted + (decPart ? '.' + decPart : '');
}

所有方法对比

方法代码量可读性面试场景
toLocaleString1 行★★★★★实际开发首选
正则3 行★★★面试最常问
循环遍历15 行★★★★基础扎实
reduce8 行★★函数式思维

常见面试追问

Q1: 如何处理各种边界情况?

答案

function formatNumber(num: number | string): string {
// 处理非法输入
const n = typeof num === 'string' ? Number(num) : num;
if (!Number.isFinite(n)) return String(num);

const [intPart, decPart] = String(n).split('.');
const formatted = intPart.replace(/\B(?=(\d{3})+$)/g, ',');
return decPart !== undefined ? `${formatted}.${decPart}` : formatted;
}

// 边界测试
formatNumber(0); // "0"
formatNumber(-0); // "0"
formatNumber(123); // "123"(不足 4 位不添加)
formatNumber(NaN); // "NaN"
formatNumber(Infinity); // "Infinity"
formatNumber(0.001); // "0.001"
formatNumber(-1234.5); // "-1,234.5"

Q2: 中文数字格式化(万/亿)?

答案

function formatChinese(num: number): string {
const abs = Math.abs(num);
const sign = num < 0 ? '-' : '';

if (abs >= 1e8) {
return sign + (abs / 1e8).toFixed(2) + '亿';
}
if (abs >= 1e4) {
return sign + (abs / 1e4).toFixed(2) + '万';
}
return sign + abs.toLocaleString('en-US');
}

formatChinese(123456789); // "1.23亿"
formatChinese(12345); // "1.23万"
formatChinese(1234); // "1,234"

Q3: 表单中实时格式化输入的数字?

答案

function handleInput(e: InputEvent): void {
const input = e.target as HTMLInputElement;
// 去掉非数字和小数点
const clean = input.value.replace(/[^\d.]/g, '');
// 格式化
const num = parseFloat(clean);
if (!isNaN(num)) {
// 记住光标位置
const cursorPos = input.selectionStart ?? 0;
const oldLen = input.value.length;

input.value = formatNumber(num);

// 调整光标位置
const newLen = input.value.length;
input.setSelectionRange(
cursorPos + (newLen - oldLen),
cursorPos + (newLen - oldLen)
);
}
}
国际化注意

不同地区的千分位分隔符不同:

  • 美国/中国:1,234,567.89(逗号分隔,点号小数)
  • 德国/法国:1.234.567,89(点号分隔,逗号小数)

使用 Intl.NumberFormat 可以自动适配地区。

相关链接