文章目录
问题描述
最近面试遇到了一个数字格式转化的问题:把一个大数(位数很多)用金融数字样式表示。即使用千位分隔符 —— 三位一个逗号间隔。比如:
- 1234567890 --> 1,234,567,890
- 5236 --> 5,236
这种的数字格式生活中也是非常常见,比如支付宝花呗额度:
方案一:Number.prototype.toLocaleString()
toLocaleString()
这个方法作用是把数字转换为字符串并使用三位分节法进行显示。如果是浮点数,还会进行四舍五入并保留三位小数的操作。
如果没有其他的格式要求,toLocaleString()
绝对是最简单的方案。
function formatNumber(num){
return num.toLocaleString();
}
//测试
console.log(formatNumber(1384125862));
console.log(formatNumber(1234567890.125462));
//输出
1,384,125,862
1,234,567,890.125
方案二:正则表达式
这种字符串匹配的操作完全可以用正则表达式来解决。这里优化一点:如果是浮点数,还支持再传一个参数decimal
来指定四舍五入后保留的小数位数。
function formatNumber(num,decimal){
let str = num.toFixed(decimal).toString();
let reg = str.indexOf(".") > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g;
return str.replace(reg,"$1,");
}
该方法的核心思路是:利用正则表达式匹配到每三位数字的末位数,然后在其后添加逗号,
1384125862..toString().match(/(\d)(?=(?:\d{3})+$)/g)
// [ '1', '4', '5' ]
添加逗号,
的操作借助的是字符串对象原型的replace
方法,该方法本身是用于字符串替换的,这里通过添加$1
的位置标识符,来巧妙的完成添加操作。
测试输出一致:
//测试
console.log(formatNumber(1384125862));
console.log(formatNumber(1234567890.125462,3));
//输出
1,384,125,862
1,234,567,890.125
方案三:原生JS —— 数组map()方法遍历
那如果不借用现有的实例方法,让你自己动手去用原生JS实现呢?我当时在面试时就比较蠢,前两种方法都没想到,直接用的是原生JS实现。
考虑到浮点数的情况,就仍然先进行四舍五入保留小数的操作。然后根据小数点把数据拆分为整数和小数两部分。
function formatNumber(num,decimal){
var splits=[],res=[];
var splits = num.toFixed(decimal).toString().split(".");
splits[0].split("").reverse().map(function(item,index){
if(index % 3 == 0 && index != 0){
res.push(",");
}
res.push(item);
});
return res.reverse().join("")+(splits.length>1 ? "."+splits[1] : "");
}
接下来着重处理整数部分splits[0]
:
继续拆分成单字符的数组,然后逆序排列后每3位插入一个逗号(因为逗号是从后往前加)。具体用的是map
方法遍历,把每一项item
都重新添加到res
这个输出数组中。其中每一次都判断下标位置,在能被3整除并且是非零位时,就加入一个逗号,
如果遍历结束后输出res
的逆序形式的话,将会是这个样子,以1384125862
为例:
[
'1', ',', '3', '8',
'4', ',', '1', '2',
'5', ',', '8', '6',
'2'
]
之后再进行一次res
数组的逆序排列,重新join
成字符串。最后再判断一次是否有小数部分,有的话加上即可。
测试输出仍然一致:
//测试
console.log(formatNumber(1384125862));
console.log(formatNumber(1234567890.125462,3));
//输出
1,384,125,862
1,234,567,890.125
方案四:原生JS —— 数组splice()方法截取
后来又想到还可以利用数组截取元素的方式来编写算法。仍然是先拿到整数部分splitNum[0]
,然后拆分成单字符数组arr
。
function formatNumber(num,decimal){
let splitNum = num.toFixed(decimal).toString().split(".");
let arr = splitNum[0].split("");
let res = [];
while(arr.length > 0){
res.unshift(arr.splice(-3).join(""));
}
return res.join(",")+(splitNum.length > 1 ? "."+splitNum[1] : "");
}
核心API是数组的splice()
方法,由于我们是从后往前每三位一个逗号分隔,所以我们需要每次截取末尾三位元素,这时候给splice
方法传参-3
就可以完美解决。而且splice()
方法本身是会改变原数组的,这正合我意,正好可以用来做while循环的条件判断:
每次循环都会截取掉数组arr
末尾的三个元素,并添加到res
输出数组中(unshift
每次都添加到数组头部,也完美解决了顺序问题),此外只要数组arr
长度不为零就可以一直循环。
如果循环结束后输出res
数组的话,会是这样的,仍以1384125862
为例:
[ '1', '384', '125', '862' ]
处理到这里就很简单了,直接以逗号为间隔重新转换成字符串。最后再判断一下是否有小数部分,再加上即可。测试输出结果如下:
//测试
console.log(formatNumber(1384125862));
console.log(formatNumber(1234567890.125462,3));
//输出
1,384,125,862
1,234,567,890.125