使用正则表达式匹配数字 - 仅包含数字和逗号

57

我不知道如何构建适用于以下示例值的正则表达式:

123,456,789
-12,34
1234
-8

你能帮我吗?


2
“-12,34” 被认为是有效的吗?在日常使用中,它并不是有效的,并且会对结果正则表达式产生巨大影响。 - jason
15
',' 等于疯狂的欧洲人 '.' - Andrew
2
@user278618,如果您编辑个人资料,就可以将您的名称从“user278618”更改为任何您喜欢的名称。 - tchrist
3
不是每个人都使用“.”来分隔整数和小数部分... - Joey
只有数字和逗号?那就是[\d,]+ - user557597
显示剩余7条评论
10个回答

496

什么是数字?

我对你的“简单”问题有一个简单的问题:你所说的“数字”具体指的是什么?

  • −0是一个数字吗?
  • 你对√−1有何感想?
  • 是一个数字吗?
  • 186,282.42±0.02英里/秒是一个数字吗,还是两个或三个数字?
  • 6.02e23是一个数字吗?
  • 3.141_592_653_589是一个数字吗?π−2π⁻³ ͥ呢?
  • 0.083̄中有多少个数字?
  • 128.0.0.1中有多少个数字?
  • 代表什么数字?⚂⚃呢?
  • 10,5 mm中有一个数字吗,还是有两个数字?
  • ∛8³是一个数字吗,还是三个数字?
  • ↀↀⅮⅭⅭⅬⅫ AUC代表哪个数字,2762还是2009?
  • ४५६७৭৮৯৮是数字吗?
  • 03770xDEADBEEF0b111101101呢?
  • Inf是一个数字吗?NaN呢?
  • ④②是一个数字吗?呢?
  • 你对有何感想?
  • ℵ₀ℵ₁与数字有什么关系?或者呢?

建议的模式

另外,您是否熟悉这些模式?您能解释每个模式的优缺点吗?

  1. /\D/
  2. /^\d+$/
  3. /^\p{Nd}+$/
  4. /^\pN+$/
  5. /^\p{Numeric_Value:10}$/
  6. /^\P{Numeric_Value:NaN}+$/
  7. /^-?\d+$/
  8. /^[+-]?\d+$/
  9. /^-?\d+\.?\d*$/
  10. /^-?(?:\d+(?:\.\d*)?|\.\d+)$/
  11. /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
  12. /^((\d)(?(?=(\d))|$)(?(?{ord$3==1+ord$2})(?1)|$))$/
  13. /^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$/
  14. /^(?:(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}):(?:[0-9a-fA-F]{1,2}))$/
  15. /^(?:(?:[+-]?)(?:[0123456789]+))$/
  16. /(([+-]?)([0123456789]{1,3}(?:,?[0123456789]{3})*))/
  17. /^(?:(?:[+-]?)(?:[0123456789]{1,3}(?:,?[0123456789]{3})*))$/
  18. /^(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/
  19. /^(?:(?i)(?:[+-]?)(?:(?=[01]|[.])(?:[01]{1,3}(?:(?:[,])[01]{3})*)(?:(?:[.])(?:[01]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[01]+))|))$/
  20. /^(?:(?i)(?:[+-]?)(?:(?=[0123456789ABCDEF]|[.])(?:[0123456789ABCDEF]{1,3}(?:(?:[,])[0123456789ABCDEF]{3})*)(?:(?:[.])(?:[0123456789ABCDEF]{0,}))?)(?:(?:[G])(?:(?:[+-]?)(?:[0123456789ABCDEF]+))|))$/
  21. /((?i)([+-]?)((?=[0123456789]|[.])([0123456789]{1,3}(?:(?:[_,]?)[0123456789]{3})*)(?:([.])([0123456789]{0,}))?)(?:([E])(([+-]?)([0123456789]+))|))/
我怀疑上述一些模式可能符合您的需求。但我无法告诉您哪一个或哪些 - 或者,如果没有,提供另一个 - 因为您没有说明“数字”的含义。
正如您所看到的,有大量的数字可能性:实际上可能有ℵ₁个。 ☺

建议模式的关键

下面列出的每个编号解释都描述了相应编号模式的模式。
  1. 如果字符串中有任何非数字字符,包括换行符等空格,则匹配。
  2. 仅在字符串仅包含数字时匹配,可能除了尾随的换行符。注意,数字被定义为具有常规类别十进制数字的属性,可用作\p{Nd}\p{Decimal_Number}\p{General_Category=Decimal_Number}。这实际上只是那些数值类型类别为十进制的代码点的反映,可用作\p{Numeric_Type=Decimal}
  3. 大多数正则表达式语言中与2相同。Java在这里是个例外,因为它不将简单的字符类转义如\w\W\d\D\s\S以及\b\B映射到适当的 Unicode 属性中。这意味着您不能使用这八个一字符转义中的任何一个来处理 Java 中的任何 Unicode 数据,因为它们只对 ASCII 有效,即使 Java 内部始终使用 Unicode 字符。
  4. 与3略有不同,它不仅限于十进制数,而且可以是任何数字;也就是说,任何具有\pN\p{Number}\p{General_Category=Number}属性的字符。这些包括用于罗马数字的\p{Nl}\p{Letter_Number}和用于上下标和圆形数字的\p{No}\p{Other_Number}等。
  5. 仅匹配由数字组成且其十进制值为10的字符串,因此像罗马数字十()和之类的东西。
  6. 仅匹配包含缺乏 Numeric Value NaN 的字符的字符串;换句话说,所有字符都必须具有某些数值。
  7. 仅匹配十进制数,可选带有前导 HYPHEN MINUS。
  8. 与7相同,但现在也适用于符号为加号而不是减号的情况。
  9. 查找十进制数,可选带有 HYPHEN MINUS 和可选的 FULL STOP,后跟零个或多个小数位数。
  10. 与9相同,但如果点后面有数字,则不需要点前面有数字。
  11. C 和许多其他语言的标准浮点表示法,允许科学计数法。
  12. 查找仅由两个或更多任何脚本的十进制数组成的数字,按降序排列,如 987 或 54321。这个递归的正则表达式包括一个调用到 Perl 代码的回调,该代码检查前瞻数字是否具有当前数字的后继代码点值;也就是说,它的序数值比当前数字大一。可以使用 C 函数作为回调,在 PCRE 中执行此操作。
  13. 查找具有有效范围内的四个十进制数的有效 IPv4 地址,如 128.0.0.1 或 255.255.255.240,但不包括 999.999.999.999。
  14. 查找有效的 MAC 地址,

    来源和可维护性

    模式1、2、7-11来自于先前版本的Perl“常见问题”列表中的问题“如何验证输入?”。该部分已被建议使用AbigailDamian Conway编写的Regexp::Common模块代替。原始模式仍可在Perl Cookbook的Recipe 2.1中找到,“检查字符串是否为有效数字”,其中包括各种不同语言的解决方案,例如ada、common lisp、groovy、guile、haskell、java、merd、ocaml、php、pike、python、rexx、ruby和tcl,可以在the PLEAC project上找到。

    模式12可以更清晰地重写

    m{
        ^
        (
            ( \d )
            (?(?= ( \d ) ) | $ )
            (?(?{ ord $3 == 1 + ord $2 }) (?1) | $ )
        )
        $
    }x
    

    它使用正则表达式递归,这在许多模式引擎中都可以找到,包括Perl和所有基于PCRE的语言。但它还使用了嵌入式代码调用作为第二个条件模式的测试;据我所知,代码调用仅在Perl和PCRE中可用。

    模式13-21是从上述Regexp::Common模块派生的。请注意,为简洁起见,这些都没有写入您在生产代码中肯定需要的空格和注释。以下是在/x模式下的示例:

    $real_rx = qr{ (   # start $1 to hold entire pattern
        ( [+-]? )                  # optional leading sign, captured into $2
        (                          # start $3
            (?=                    # look ahead for what next char *will* be
                [0123456789]       #    EITHER:  an ASCII digit
              | [.]                #    OR ELSE: a dot
            )                      # end look ahead
            (                      # start $4
               [0123456789]{1,3}       # 1-3 ASCII digits to start the number
               (?:                     # then optionally followed by
                   (?: [_,]? )         # an optional grouping separator of comma or underscore
                   [0123456789]{3}     # followed by exactly three ASCII digits
               ) *                     # repeated any number of times
            )                          # end $4
            (?:                        # begin optional cluster
                 ( [.] )               # required literal dot in $5
                 ( [0123456789]{0,} )  # then optional ASCII digits in $6
            ) ?                        # end optional cluster
         )                         # end $3
        (?:                        # begin cluster group
            ( [E] )                #   base-10 exponent into $7
            (                      #   exponent number into $8
                ( [+-] ? )         #     optional sign for exponent into $9
                ( [0123456789] + ) #     one or more ASCII digits into $10
            )                      #   end $8
          |                        #   or else nothing at all
        )                          # end cluster group
    ) }xi;          # end $1 and whole pattern, enabling /x and /i modes
    

    从软件工程的角度来看,上面的/x模式版本仍存在一些问题。首先,有很多代码重复,你会看到相同的[0123456789];如果其中一个序列意外地漏掉了一个数字,会发生什么?其次,你依赖于位置参数,必须计数。这意味着你可能会写出像这样的代码:

    (
      $real_number,          # $1
      $real_number_sign,     # $2
      $pre_exponent_part,    # $3
      $pre_decimal_point,    # $4
      $decimal_point,        # $5
      $post_decimal_point,   # $6
      $exponent_indicator,   # $7
      $exponent_number,      # $8
      $exponent_sign,        # $9
      $exponent_digits,      # $10
    ) = ($string =~ /$real_rx/);
    

    这实在是太糟糕了!容易弄错编号,很难记住符号名称的位置,而且写起来很繁琐,特别是如果你不需要所有这些部分。将其改为使用命名组而不仅仅是编号组。同样,我将使用 Perl 语法来表示变量,但 Pattern 的内容应该适用于任何支持命名组的地方。

    use 5.010;              # Perl got named patterns in 5.10
    $real_rx = qr{
      (?<real_number>
        # optional leading sign
        (?<real_number_sign> [+-]? )
        (?<pre_exponent_part>
            (?=                         # look ahead for what next char *will* be
                [0123456789]            #    EITHER:  an ASCII digit
              | [.]                     #    OR ELSE: a dot
            )                           # end look ahead
            (?<pre_decimal_point>
                [0123456789]{1,3}       # 1-3 ASCII digits to start the number
                (?:                     # then optionally followed by
                    (?: [_,]? )         # an optional grouping separator of comma or underscore
                    [0123456789]{3}     # followed by exactly three ASCII digits
                ) *                     # repeated any number of times
             )                          # end <pre_decimal_part>
             (?:                        # begin optional anon cluster
                (?<decimal_point> [.] ) # required literal dot
                (?<post_decimal_point>
                    [0123456789]{0,}  )
             ) ?                        # end optional anon cluster
       )                                # end <pre_exponent_part>
       # begin anon cluster group:
       (?:
           (?<exponent_indicator> [E] ) #   base-10 exponent
           (?<exponent_number>          #   exponent number
               (?<exponent_sign>   [+-] ?         )
               (?<exponent_digits> [0123456789] + )
           )                      #   end <exponent_number>
         |                        #   or else nothing at all
       )                          # end anon cluster group
     )                            # end <real_number>
    }xi;
    

    现在抽象化已经被命名,这很有帮助。您可以通过名称将组分离出来,只需要关心您所需的那些。例如:
    if ($string =~ /$real_rx/) {
        ($pre_exponent, $exponent_number) =
            @+{ qw< pre_exponent exponent_number > };
    }
    

    还有一件事情可以做来让这个模式更易于维护。问题在于仍然存在太多的重复,这意味着它在一个地方很容易被修改,但在另一个地方却不容易。如果你正在进行McCabe分析,你会说它的复杂度指标太高。大多数人会说它缩进太多了,这使得它难以跟踪。为了解决所有这些问题,我们需要一个“语法模式”,其中包含一个定义块来创建命名的抽象,然后我们稍后在匹配中将其视为子程序调用。

    use 5.010;              # Perl first got regex subs in v5.10
    $real__rx = qr{ 
    
        ^                   # anchor to front
        (?&real_number)     # call &real_number regex sub
        $                   # either at end or before final newline
    
      ##################################################
      # the rest is definition only; think of         ##
      # each named buffer as declaring a subroutine   ##
      # by that name                                  ##
      ##################################################
      (?(DEFINE)
          (?<real_number>
              (?&mantissa)
              (?&abscissa) ?
    
          )
          (?<abscissa>
              (?&exponent_indicator)
              (?&exponent)
          )
          (?<exponent>
              (&?sign)    ?
              (?&a_digit) +
          )
          (?<mantissa>
             # expecting either of these....
             (?= (?&a_digit)
               | (?&point)
             )
             (?&a_digit) {1,3}
             (?: (?&digit_separator) ?
                 (?&a_digit) {3}
             ) *
             (?: (?&point)
                 (?&a_digit) *
             ) ?
          )
          (?<point>               [.]     )
          (?<sign>                [+-]    )
          (?<digit_separator>     [_,]    )
          (?<exponent_indicator>  [Ee]    )
          (?<a_digit>             [0-9]   )
       ) # end DEFINE block
    }x;
    

    看看这个语法模式比原来的嘈杂模式好多了?而且更容易掌握语法:我甚至没有一个需要纠正的正则表达式语法错误。 (好吧,没错,我其他的输入也没有任何语法错误,但我已经做了一段时间了。 :)

    语法模式看起来更像BNF,而不是人们开始讨厌的丑陋的正则表达式。 它们更容易阅读,编写和维护。 所以,不要再使用丑陋的模式了,好吗?


7
@ThiefMaster,我希望你现在能够详细解答我的原始问题,使其更加易懂,不过请不要改变原意。希望你现在会开心地微笑! :) - tchrist
2
@ʞɔɐɯɹoↃɔW sǝɯɐſ:不知道如何在Microsoft中实现,但是使用最新的Perl版本非常简单:perl -Mv5.14 -MUnicode::UCD=num -CSA -E 'say "$_ is ",num($_) for @ARGV' "४५६७" "໓໑໔໑໕໙"会输出४५६७ is 4567 ໓໑໔໑໕໙ is 314159。这意味着对于数字字符串,您只需要使用/(\d+)/获取它们并调用Unicode::UCD::num函数即可。我不知道Microsoft的国际支持有多好,但肯定比Java好。 - tchrist
2
很棒的答案-但是你有21个模式和20个解释。 :) - Anthony Mills
15
在一粒沙子中看到宇宙 - 正则表达式、可读性、问题的框架、鲁棒性、清晰度、启示 - 一小时内的永恒 - 这个答案压缩了多少智慧? - Phil H
2
此答案已添加到 Stack Overflow 正则表达式 FAQ,位于“高级正则表达式 > 常见验证 > 数字”一栏中,约 2/3 处。 - aliteralmind
显示剩余19条评论

42
如果你只想允许数字和逗号,那么正则表达式是^[-,0-9]+$。如果你还想允许空格,则使用^[-,0-9 ]+$
但是,如果你想允许合法的数字,最好使用以下正则表达式:
^([-+] ?)?[0-9]+(,[0-9]+)?$

或者直接使用.net的数字解析器(有关各种NumberStyles,请参见MSDN):

try {
    double.Parse(yourString, NumberStyle.Number);
}
catch(FormatException ex) {
    /* Number is not in an accepted format */
}

1
没错,看我的补充 - 他的问题没有说明他是想允许“只包含数字和逗号”,还是想检查有效的数字。 - ThiefMaster
2
那真是相当糟糕。我认为它甚至没有回答@user278618的问题。不过,@user278618提出的问题与他提供的示例不符。在我的答案中,我提供了许多解决方案,其中没有一个有你的解决方案中存在的许多问题。我的/^(?:(?:[+-]?)(?:[0123456789]{1,3}(?:,?[0123456789]{3})*))$/可能会满足他的需求,但由于措辞不精确且存在冲突,很难确定。但肯定比你的做得好得多! - tchrist
他的问题非常不清楚,我只能猜测他到底想要什么。 - ThiefMaster

10

试一下这个:

^-?\d{1,3}(,\d{3})*(\.\d\d)?$|^\.\d\d$

允许:

1
12
.99
12.34 
-18.34
12,345.67
999,999,999,999,999.99

7
自从这个问题四年后重新打开,我想提供一个不同的看法。作为一个花费大量时间使用正则表达式的人,我的看法是:
A. 如果可能的话,不要使用正则表达式验证数字
尽可能使用您的语言。可能会有函数来帮助您确定字符串包含的值是否为有效数字。话虽如此,如果您接受各种格式(逗号等),您可能没有选择。
B. 不要手动编写正则表达式来验证数字范围
  • 编写匹配给定范围内数字的正则表达式很难。即使编写一个匹配1到10之间数字的正则表达式,也可能出错。
  • 一旦你有了数字范围的正则表达式,调试就很难。首先,它看起来很糟糕。其次,你怎么确定它匹配了你想要的所有值,而没有匹配任何你不想要的值呢?老实说,如果你自己做,没有同事在你身边帮忙,你是无法确定的。最好的调试技巧是通过编程输出整个数字范围,并将它们与正则表达式进行比较。
  • 幸运的是,有工具可以自动生成数字范围的正则表达式。

C.明智地使用正则表达式工具来节省你的正则表达式能量

在给定范围内匹配数字的问题已经得到解决。你不需要试图重新发明轮子。这是一个可以通过程序机械地解决的问题,而且保证没有错误。充分利用这个免费的方法。
为了学习目的,解决数字范围正则表达式可能有趣一两次。除此之外,如果你有精力投入到进一步提高正则表达式技巧上,可以把它花在有用的事情上,比如深入理解 贪婪正则表达式,阅读 Unicode 正则表达式,尝试零宽度匹配或递归,阅读 SO 正则表达式 FAQ 并发现一些很棒的技巧,比如如何 从正则表达式匹配中排除某些模式……或者阅读经典著作,比如 精通正则表达式(第三版)正则表达式菜谱(第二版)

对于工具,您可以使用:

  • 在线工具:Regex_for_range
  • 离线工具:我所知道的唯一一个是由正则表达式专家Jan Goyvaerts开发的RegexMagic(非免费)。这是他的初学者正则表达式产品,据我回忆,它拥有在给定范围内生成数字的许多选项以及其他功能。
  • 如果条件过于复杂,请自动生成两个范围...然后使用交替运算符|将它们连接起来。

D. 练习:构建用于问题规格的正则表达式

这些规格相当广泛...但不一定模糊。 让我们再次查看示例值:

123,456,789
-12,34
1234
-8

第一个和第二个值有什么关系?在第一个中,逗号匹配三的幂次方组。在第二个中,它可能匹配欧洲大陆风格数字格式中的小数点。这并不意味着我们应该允许到处都有数字,比如1,2,3,44。同样,我们也不应该太过严格。例如,接受答案中的正则表达式将无法匹配123,456,789这一要求(请参见demo)。

我们如何构建正则表达式以满足规格要求?

  • 让我们使用^$将表达式锚定,以避免子匹配
  • 让我们允许可选的负号:-?
  • 让我们匹配两种类型的数字,在交替项(?:this|that)的两侧:
  • 在左边,是一个欧洲风格的数字,其小数部分可以选择性地带有逗号:[1-9][0-9]*(?:,[0-9]+)?
  • 在右边,是一个带有千位分隔符的数字:[1-9][0-9]{1,2}(?:,[0-9]{3})+

完整的正则表达式:

^-?(?:[1-9][0-9]*(?:,[0-9]+)?|[1-9][0-9]{1,2}(?:,[0-9]{3})+)$

请查看演示

这个正则表达式不允许以0开头的欧洲风格数字,比如0,12。这是一个特性,而不是错误。如果想要匹配它们,只需要进行一点小调整:

^-?(?:(?:0|[1-9][0-9]*)(?:,[0-9]+)?|[1-9][0-9]{1,2}(?:,[0-9]{3})+)$

请查看演示


4
^[-+]?(\d{1,3})(,?(?1))*$

正则表达式可视化

Debuggex演示

那么它是什么?!

  • ^ 表示字符串开头
  • [-+]? 允许在字符串开头后面加上 负号正号
  • (\d{1,3}) 匹配至少一位数字且最多三位数字 ({1,3}) 并将连续的数字作为第一组进行分组(括号 (...) 建立了一个新组)
  • (,?(?1))* 好吧... 让我们来分解一下
    • (...) 建立另一个组(不是很重要
    • ,? 匹配在第一组数字后面可能存在的逗号
    • (?1) 再次匹配第一组的模式(记得 (\d{1,3}));换言之:此时表达式已经匹配了一个符号(加号/减号/无符号)后面跟着一串数字,可能还有一个逗号,再跟着另一串数字。
    • (,?(?1))* 中的 * 将第二部分(逗号和数字串)重复匹配尽可能多次
  • $ 最后匹配字符串结尾

这种表达式的好处在于,避免了反复定义相同的模式;缺点是有时会变得比较复杂 :-/


这个不处理浮点数 -- 尤其是小数点作为十进制的情况。 - mickmackusa
这是真的,但这不是目标……它与请求的输入相匹配。 - bukart
它还匹配了1,2,3,4,5,这并不是要求的。https://regex101.com/r/T3m0iQ/1你的模式没有区分逗号作为千位分隔符和逗号作为小数点的用法。验证不是很强。 - mickmackusa

3

试试这个:

^-?[\d\,]+$

它将允许可选的-作为第一个字符,然后是逗号和数字的任意组合。

这是一个明显薄弱的验证,正如Paul在先前的评论中指出的那样。 - mickmackusa

3
^-?    # start of line, optional -
(\d+   # any number of digits
|(\d{1,3}(,\d{3})*))  # or digits followed by , and three digits
((,|\.)\d+)? # optional comma or period decimal point and more digits
$  # end of line

为什么不将(,|\.)简化为[,.]呢? - mickmackusa

0
在Java中,你可以使用java.util.Scanner和它的useLocale方法。
Scanner myScanner =  new Scanner(input).useLocale( myLocale)

isADouble = myScanner.hasNextDouble()

-3

试试这个:

    boxValue = boxValue.replace(/[^0-9\.\,]/g, "");

这个正则表达式只匹配数字、点和逗号。


为什么要发布这个?它与已经高赞的答案没有任何不同之处。 - Shawn Mehan

-3

关于示例:

    ^(-)?([,0-9])+$

应该可以工作。使用任何您想要的语言来实现它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接