什么是数字?
我对你的“简单”问题有一个简单的问题:“数字”到底是什么意思?
-
−0 是数字吗?
- 你觉得
√−1怎么样?
-
⅝ 或 ⅔ 是数字吗?
-
186,282.42±0.02miles/second 是一个数字 - 还是两个或三个?
-
6.02e23 是一个数字吗?
-
3.141_592_653_589 是一个数字吗? π 或 ℯ 怎么样?还有−2π⁻³ ͥ?
-
0.083̄ 中有多少个数字?
-
128.0.0.1 中有多少个数字?
-
⚄ 持有什么号码? ⚂⚃ 怎么样?
-
10,5 mm 中是否有一个数字 - 还是有两个?
-
∛8³ 是一个数字,还是三个?
-
ↀↀⅮⅭⅭⅬⅫ AUC 代表什么数字,2762 还是 2009?
-
४५६७ 和 ৭৮৯৮ 是数字吗?
-
0377、0xDEADBEEF 和 0b111101101 呢?
-
Inf 是一个数字吗?是NaN?
-
④② 是数字吗? ⓰呢?
- 你觉得
㊅怎么样?
-
ℵ₀ 和 ℵ₁ 与数字有什么关系?还是ℝ、ℚ 和ℂ?
建议的模式
另外,你熟悉这些模式吗?你能解释一下每种方法的优缺点吗?
/\D/
/^\d+$/
/^\p{Nd}+$/
/^\pN+$/
/^\p{Numeric_Value:10}$/
/^\P{Numeric_Value:NaN}+$/
/^-?\d+$/
/^[+-]?\d+$/
/^-?\d+\.?\d*$/
/^-?(?:\d+(?:\.\d*)?|\.\d+)$/
/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
/^((\d)(?(?=(\d))|$)(?(?{ord$3==1+ord$2})(?1)|$))$/
/^(?:(?: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}))$/
/^(?:(?:[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}))$/
/^(?:(?:[+-]?)(?:[0123456789]+))$/
/(([+-]?)([0123456789]{1,3}(?:,?[0123456789]{3})*))/
/^(?:(?:[+-]?)(?:[0123456789]{1,3}(?:,?[0123456789]{3})*))$/
/^(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/
/^(?:(?i)(?:[+-]?)(?:(?=[01]|[.])(?:[01]{1,3}(?:(?:[,])[01]{3})*)(?:(?:[.])(?:[01]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[01]+))|))$/
/^(?:(?i)(?:[+-]?)(?:(?=[0123456789ABCDEF]|[.])(?:[0123456789ABCDEF]{1,3}(?:(?:[,])[0123456789ABCDEF]{3})*)(?:(?:[.])(?:[0123456789ABCDEF]{0,}))?)(?:(?:[G])(?:(?:[+-]?)(?:[0123456789ABCDEF]+))|))$/
/((?i)([+-]?)((?=[0123456789]|[.])([0123456789]{1,3}(?:(?:[_,]?)[0123456789]{3})*)(?:([.])([0123456789]{0,}))?)(?:([E])(([+-]?)([0123456789]+))|))/
我怀疑上面的一些模式可能满足您的需求。但我不能告诉你是哪一个或几个——或者,如果没有,提供给你另一个——因为你还没有说出你所说的“数字”是什么意思。
如您所见,有大量的数字可能性:实际上,很可能有 ℵ₁ 的价值。 ☺
建议模式的关键
下面列出的每个编号说明都描述了上面列出的相应编号模式的模式。
- 如果字符串中的任何位置有任何非数字,则匹配,包括换行符之类的空格。
- 仅当字符串只包含数字时才匹配,可能的例外是尾随换行符。请注意,数字定义为具有通用类别十进制数属性,可用作
\p{Nd}、\p{Decimal_Number} 或\p{General_Category=Decimal_Number}。这实际上只是那些数字类型类别为十进制的代码点的反映,可以通过\p{Numeric_Type=Decimal}获得。
- 这与大多数正则表达式语言中的 2 相同。 Java 在这里是一个例外,因为它不会将简单的 charclass 转义(如
\w 和 \W、\d 和 \D、\s 和 \S 以及 \b 或 \B 映射到适当的 Unicode 属性。这意味着您不能对 Java 中的任何 Unicode 数据使用这八个单字符转义中的任何一个,因为它们仅适用于 ASCII,即使 Java 始终在内部使用 Unicode 字符。
- 这与 3 略有不同,它不限于十进制数,而是可以是任何数字;那是,
具有
\pN、\p{Number} 或\p{General_Category=Number} 属性的任何字符。其中包括用于罗马数字之类的 \p{Nl} 或 \p{Letter_Number} 以及用于下标和下标数字、分数和带圆圈的数字的 \p{No} 或 \p{Other_Number} - 其中包括计数棒。
- 这仅匹配那些完全由十进制值为 10 的数字组成的字符串,例如
Ⅹ 罗马数字 10 和 ⑩、⑽、⒑、⓾、❿、 ➉ 和 ➓。
- 仅那些包含缺少数值 NaN 的字符的字符串;换句话说,所有字符都必须有一些数值。
- 仅匹配十进制数字,可以选择与前导连字符减号匹配。
- 与 7 相同,但现在如果符号是加号而不是减号也可以使用。
- 查找十进制数字,后面带有可选的 HYPHEN MINUS 和可选的 FULL STOP 加上零个或多个十进制数字。
- 与 9 相同,但如果点后面有数字,则不需要数字。
- 针对 C 和许多其他语言的标准浮点表示法,允许使用科学计数法。
- 以降序查找仅由任何脚本的两位或多位小数组成的数字,例如 987 或 54321。此递归正则表达式包括对 Perl 代码的标注,用于检查前瞻数字是否具有作为当前数字;也就是说,它的序数值大一。可以在 PCRE 中使用 C 函数作为标注来执行此操作。
- 这会查找有效范围内具有四个十进制数字的有效 IPv4 地址,例如 128.0.0.1 或 255.255.255.240,但不是 999.999.999.999。
- 这会查找有效的 MAC 地址,即六个冒号分隔的两个 ASCII 十六进制数字对。
- 这会查找 ASCII 范围内带有可选前导符号的整数。这是匹配 ASCII 整数的正常模式。
- 这类似于 15,只是它需要一个逗号来分隔三个一组。
- 这类似于 15,只是用于分隔组的逗号现在是可选的。
- 这是在 ASCII 中匹配 C 样式浮点数的常规模式。
- 这类似于 18,但需要逗号来分隔 3 组,并且以 2 为底,而不是以 10 为底。
- 这类似于 19,但是是十六进制的。请注意,可选指数现在由 G 而不是 E 表示,因为 E 是有效的十六进制数字。
- 这将检查字符串是否包含 C 样式的浮点数,但在它们之间每三位逗号或下划线 (LOW LINE) 有一个可选的分组分隔符。它还会将该字符串存储到
\1 捕获组中,在匹配成功后以$1 的形式提供。
来源和可维护性
模式编号 1、2、7–11 来自问题“如何验证输入?”中的 Perl 常见问题列表的前一个版本。该部分已替换为使用Regexp::Common 模块的建议,由Abigail 和Damian Conway 编写。原始模式仍然可以在 Perl Cookbook 的配方 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
它使用regex recursion,它在许多模式引擎中都可以找到,包括 Perl 和所有 PCRE 派生的语言。但它也使用嵌入式代码标注作为第二个条件模式的测试;据我所知,代码标注仅在 Perl 和 PCRE 中可用。
模式 13-21 源自前面提到的 Regexp::Common 模块。请注意,为简洁起见,这些都是在没有您在生产代码中肯定需要的空格和 cmets 的情况下编写的。这是/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,而不是人们讨厌的丑陋的旧正则表达式。它们更容易阅读、编写和维护。所以我们不要再有丑陋的模式了,好吗?