通过正则表达式格式化货币数字

我今天遇到了一个有趣的要求,就是要把一个浮动数作为货币数字来格式化。在美国和许多其他国家,大于或等于 1000 个单位($1,000.00)的货币数字通常会通过逗号分隔符分解成三元组。我最初的反应是使用递归函数,但我看了一下 Rails 的佳作 ActiveSupport (opens new window),看看他们的 number_with_delimiter 方法是如何执行同样的任务的。令我惊讶的是,我发现这个任务是通过一个巧妙的正则表达式来处理的。

/(\d)(?=(\d{3})+(?!\d))/

这使用了一些我认为比较高级的正则表达式的概念:正向查找和负向查找。

正向查找

引自 Rdoc 页面 Ruby 的正则表达式类:

ensures that the following characters match pat, but doesn't include those characters in the matched text

确保后面的字符与 pattern(?=pattern) 匹配,但不将这些字符包含在匹配的文本中

在我们的表达式中可以看到,我们正在寻找一个数字,那么我们使用正向查找的 ?= 语法来搜索一个或多个数字三元组的实例。这样就可以在模式 1234 中匹配 1,在模式 123456 中匹配 123。

负向查找

同样引自 Rdoc 页面:

ensures that the following characters do not match pat, but doesn't include those characters in the matched text

确保后面的字符与 pattern(?!pattern) 不匹配,但不将这些字符包含在匹配的文本中

通过负向查找 ?!,这是正向查找的反转。我们进一步定义了我们的表达式,只捕捉跟随另一个数字的数字。将其添加到我们的正向查找规则中,我们只选择位置是 3 的倍数的数字,当该数字前面有另一个数字时。这是一个口号,但这意味着我们排除了已经存在的逗号。

插入逗号

现在我们已经声明了我们的表达式,我们可以在字符串替换调用中使用这个表达式,在我们确定的位置使用反向引用插补我们的逗号,下面通过逗号分割(或任何其他分隔符):

在 Ruby 中:

"10000".gsub(/(\d)(?=(\d{3})+(?!\d))/, "\\1,")
# => "10,000"

在 JavaScript 中:

'10000'.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
// => "10,000"

字符串替换方法的第二个参数是要替换我们的模式的值。这里我们看到我们的方向引用后面加了一个逗号。

总结

正则表达式是一个强大的工具。这个模式给我留下深刻印象的是,它是如此简洁,却消除了对更复杂的递归函数调用或循环的需求。这里的模式是为我们的定界符定义插入点,同时尊重货币数字格式化的所有规则。通过窥视引擎盖下的东西,你会学到很多东西!