小技巧之千分位格式化数字

stirng.gsub 和 正则 实现 千分位格式化数字


问题

接到了个需求:
千分位格式化数字,可能有小数点。
比如:
1234567 -> 1,234,567
1234567.8901 -> 1,234,567.8901

最后效果就是小数点左边的部分,每三个数字加一个逗号。

实现:

  • 最直接的肯定就是用字符串截取,先找到小数点,分割成两部分;再把前半部分3个为一组分开,再重新带上逗号拼接。
  • 也可以用 正则 + 字符串替换。通过模式匹配,从头开始每三个数字后加一个逗号,不断替换直到最后或小数点位置。
    第一种方法通俗易懂简单明了,但是麻烦;第二种代码相对简(xiu)洁(ji)一些。




分析

character class

字符类,用于表示字符的集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
x: (这里 x 不能是 魔法字符 ^$()%.[]*+-? 中的一员) 表示字符 x 自身。
.: (一个点)可表示任何字符。
%a: 表示任何字母。
%c: 表示任何控制字符。
%d: 表示任何数字。
%g: 表示任何除空白符外的可打印字符。
%l: 表示所有小写字母。
%p: 表示所有标点符号。
%s: 表示所有空白字符。
%u: 表示所有大写字母。
%w: 表示所有字母及数字。
%x: 表示所有 16 进制数字符号。
%x: (这里的 x 是任意非字母或数字的字符) 表示字符 x。 这是对魔法字符转义的标准方法。 所有非字母或数字的字符 (包括所有标点,也包括非魔法字符) 都可以用前置一个 '%' 放在模式串中表示自身。
[set]: 表示 set 中所有字符的联合。 可以以 '-' 连接,升序书写范围两端的字符来表示一个范围的字符集。 上面提到的 %x 形式也可以在 set 中使用 表示其中的一个元素。 其它出现在 set 中的字符则代表它们自己。 例如,[%w_] (或 [_%w]) 表示所有的字母数字加下划线), [0-7] 表示 8 进制数字, [0-7%l%-] 表示 8 进制数字加小写字母与 '-' 字符。
交叉使用类和范围的行为未定义。 因此,像 [%a-z] 或 [a-%%] 这样的模式串没有意义。
[^set]: 表示 set 的补集, 其中 set 如上面的解释。
所有单个字母表示的类别(%a,%c,等), 若将其字母改为大写,均表示对应的补集。 例如,%S 表示所有非空格的字符。


pattern item(模式条目)

模式条目可以是:

1
2
3
4
5
6
7
8
单个字符类匹配该类别中任意单个字符
单个字符类跟一个 '*', 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串;
单个字符类跟一个 '+', 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串;
单个字符类跟一个 '-', 将匹配零或更多个该类的字符。 和 '*' 不同,这个条目总是匹配尽可能短的串;
单个字符类跟一个 '?', 将匹配零或一个该类的字符。 只要有可能,它会匹配一个;
%n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。
%bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。
%f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 '\0' 一样。


pattern(模式)

指一个模式条目的序列。
在模式最前面加上符号 ‘^’ 表示从字符串的开始处做匹配。
在模式最后加上符号 ‘$’ 表示匹配到字符串的结尾。
如果 ‘^’ 和 ‘$’ 出现在其它位置,它们均没有特殊含义,只表示自身。


capture(捕获)

模式可以在内部用小括号括起来(表示一个子模式),这些子模式称为 捕获物。
当匹配成功时,由捕获物匹配到的字符串中的子串被保存起来用于之后用途。
捕获物由它们左括号的次序来编号。
ext: 空的捕获() 将捕获到当前字符串的位置(一个数字);例如,”()%a%d()” 作用到 “ltree98” 上,将产生两个捕获物: 5和7。


string.gsub

string.gsub (目标串, 匹配串, 替换串 [, 替换次数])

返回一个经过替换串替换后的目标串的副本及替换次数。
如果替换串是string则直接用替换串替换匹配串即可。当里面字符存在’%’时,则代表调用捕获子串,捕获子串序列在1-9,0代表全部,%%代表%字符。
如果替换串是table类型,在每次匹配调用,并用第一个捕获子串来作为table的key值查询。
如果替换串是function类型,在每次匹配调用,并将所有子串按顺序作为函数参数。
任何情况下,如果匹配串没有捕获子串,那么整个匹配串作为捕获子串继续执行。
如果table或者function返回的是

  • string类型,直接用
  • number类型,直接用
  • false,不替换
  • nil,不替换
  • 其他,报错




方案

1
2
3
4
5
6
7
8
9
function formatnumberthousands(num)
local formatted = tostring(num)
local k
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if k == 0 then break end
end
return formatted
end





参考资料