千分位格式化数字
问题
实现一个函数,把数字转为千分位格式的字符串。
示例:
输入: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 : '');
}
所有方法对比
| 方法 | 代码量 | 可读性 | 面试场景 |
|---|---|---|---|
| toLocaleString | 1 行 | ★★★★★ | 实际开发首选 |
| 正则 | 3 行 | ★★★ | 面试最常问 |
| 循环遍历 | 15 行 | ★★★★ | 基础扎实 |
| reduce | 8 行 | ★★ | 函数式思维 |
常见面试追问
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 可以自动适配地区。