一、背景和意义
对于如何使用正则表达式给数字添加千分位,通过AI工具或者网页搜索时很容易获得一个有BUG的答案:
num.replace(/\B(?=(\d{3})+(?!\d))/g, ',')。本文对其进行纠正,以JavaScript作为示例语言(其他编程语言也适用,但正则表达式的写法稍有差异),提供一个正确的方法。
二、常见的做法与存在问题
针对 num.replace(/\B(?=(\d{3})+(?!\d))/g, ',') 这个常见的做法,一般情况下可以获得正确的结果:
但是某些情况下得到错误的结果:
因为按照千位分的使用规则,小数部分是不添加千分位的。
另外还有一个常用的做法是 num.toLocaleString(),该做法一般情况下结果正确:
但会丧失小数精度:
三、正确的做法
正确的做法是:num.replace(/(?<=^\d+)(?=(\d{3})+(?!\d))/g, ',')。
无论是针对整数:
还是针对小数:
均能获得正确的结果。
四、方法解读
正则表达式中的 ?=(\d{3})+ 是一个正向零宽先行断言,表示字符串中某个位置后面跟着的3个、6个、9个数字,或者其他3个倍数个数字时,匹配那个位置:
(?=(\d{3})+)将匹配"1234"这个字符串中"1"前面的那个位置的空字符串,这从如下代码中得到验证:
"1"前面的位置确实是紧跟着3个数字,只是这3个数字后面还有一个数字。但我们期望匹配的位置是3个数字之后就结束了,后面不应该再跟着数字。(?=(\d{3})+$)可以实现这一效果:
其中 $ 表示字符串结束的位置,在正则表达式外加一个 g 表示每个匹配的位置均加上千分位分隔符,如果不加 g 则只有第一个匹配的位置添加分隔符:
但 (?=(\d{3})+$) 对于小数会有问题:
改进方法是将 $ 改成 (?!\d),后者表示该位置不能是数字,可以是其他字符或者结尾。执行结果如下:
前面优化得到的正则表达式为 (?=(\d{3})+(?!\d)),该表达式还有一个BUG:
字符串的最前面是不能加千分位分隔符的。一个改进方法是在前面加 \B,表示匹配的位置不能是单词的边界(一串连续的数字也可以被认为是一个单词),得到结果如下:
这个改进文案也是AI和互联网资料中经常看到的方案。前面也提到这它会给小数点右边的部分也添加分隔符。故应该将\B 改为 (?<=^\d+),这是一个正向零宽后发断言,表示当前位置左边的所有字符必须是数字。于是得到最终的正则表达式版本 (?<=^\d+)(?=(\d{3})+(?!\d))