在Perl中,我如何判断一个变量是否具有数值类型?

100
在Perl中,是否有一种简单的方法可以让我确定给定变量是否为数字?类似于以下内容:
if (is_number($x))
{ ... }

最理想的是一种在使用-w开关时不会引发警告的技术,这当然更受欢迎。

15个回答

151

使用Scalar::Util::looks_like_number()函数,它使用内置的Perl C API的looks_like_number()函数,这可能是最有效的方法。

请注意,字符串"inf"和"infinity"被视为数字。

示例:

#!/usr/bin/perl

use warnings;
use strict;

use Scalar::Util qw(looks_like_number);

my @exprs = qw(1 5.25 0.001 1.3e8 foo bar 1dd inf infinity);

foreach my $expr (@exprs) {
    print "$expr is", looks_like_number($expr) ? '' : ' not', " a number\n";
}

输出结果如下:

1 is a number
5.25 is a number
0.001 is a number
1.3e8 is a number
foo is not a number
bar is not a number
1dd is not a number
inf is a number
infinity is a number

另请参阅:


2
通常情况下,使用Perl文档查找函数的实际定义相当困难。遵循perldoc perlapi的路径告诉我们:测试SV的内容是否类似于数字(或者是数字)。即使您的atof()无法理解它们,“Inf”和“Infinity”也被视为数字(因此不会发出非数字警告)。几乎不是可测试的规范... - Day
3
Scalar::Util 中的描述很好,looks_like_number会告诉你输入是否为Perl将其视为数字的内容,这并不一定是这个问题的最佳答案。提到 atof 是无关紧要的,因为atof不属于 CORE:: 或 POSIX(您应该查看已经包含了 atof 函数并且属于POSIX的 strtod 函数),并且假设 Perl 认为的数字是有效的数值输入到C函数中显然是非常错误的。 - MkV
非常好的函数 :) 对于未定义和非数字字符串返回0,对于数字字符串返回1,对于整数返回4352,对于浮点数返回8704 :) 通常会检测到>0的数字。我已在Linux下测试过它。 - Znik
1
我总体上喜欢这个函数,但要考虑大整数。1000000 是很多零需要跟踪,容易出错,但 1,000,000 被看作是一个三元素数组,因此 Perl 接受 1_000_000,但是 looks_like_number() 却认为不是数字。让我很失望。 - Dave Jacoby
2
注意:像 0x12 这样的十六进制字符串在此测试中不被视为数字。 - Adam Katz
2
looks_like_number 暴露了 内部函数,它与 Perl 用于将字符串神奇地转换为数字的方式相同。下划线、十六进制、八进制和二进制数字仅允许作为源代码中的字面量,而不是字符串,因此例如 0+"0x15" 不起作用。(请注意,looks_like_number("0 but true") 是真的! :-) 参考资料 - haukex

29

最初的问题是如何判断一个变量是否为数字,而不是它“是否具有数值”。

有几个运算符对数字和字符串操作数有不同的操作模式,“数字”表示原来是数字或曾在数字上下文中使用过(例如在$x = “123”;0 + $x中,在加法之前,$x是一个字符串,之后被认为是数字)。

其中一种方法是:

if ( length( do { no warnings "numeric"; $x & "" } ) ) {
    print "$x is numeric\n";
}

如果启用了位运算功能,那么&就只是一个数值运算符,并添加了一个单独的字符串&.运算符,您必须禁用它:

if ( length( do { no if $] >= 5.022, "feature", "bitwise"; no warnings "numeric"; $x & "" } ) ) {
    print "$x is numeric\n";
}

(位运算符在Perl 5.022及以上版本中可用,并且如果您使用use 5.028;或更高版本,则默认启用。)


如果我将您的程序打包成一个子程序,会出现奇怪的行为,它可以正确检测非数字值,直到我尝试第一个数字值,它也被正确检测为真,但是从那之后的所有值都是真的。然而,当我在 length(...) 部分周围添加 eval 时,它始终正常工作。您有什么想法我漏掉了什么吗?sub numeric { $obj = shift; no warnings "numeric"; return eval('length($obj & "")'); } - yogibimbi
@yogibimbi:你每次都在重复使用同一个$obj变量;尝试使用my $obj = shift;。为什么要用eval? - ysth
糟糕,我的错,我使用了 my $obj = shift,当然,只是没有正确地从我的代码转移到注释中,我进行了一些编辑。但是,sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); } 产生了相同的错误。当然,有一个秘密全局变量会解释这种行为,这正是我在那种情况下所期望的,但不幸的是,事情并不那么简单。此外,这将被 strictwarnings 捕获。我尝试了 eval,这是一个相当绝望的尝试,以摆脱错误,并且它起作用了。没有更深入的推理,只是试错。 - yogibimbi
看一下: sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); }print numeric("w") . "\n"; #=>0, print numeric("x") . "\n"; #=>0, print numeric("1") . "\n"; #=>0, print numeric(3) . "\n"; #=>1, print numeric("w") . "\n"; #=>1. 如果在长度周围加上 eval(''),则最后一个打印将给出 0,就像它应该的那样。去理解吧。 - yogibimbi
@yogibimbi,我认为最有可能的情况是你没有运行你认为你在运行的代码。 - ysth

23

请查看CPAN模块Regexp::Common。我认为它可以完全满足您的需求并处理所有边缘情况(例如实数、科学计数法等)。例如:

use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }

14

通常使用正则表达式来进行数字验证。此代码将确定某个东西是否是数字,并检查未定义的变量,以避免抛出警告:


通常情况下,使用正则表达式进行数字验证。以下代码将判断一个值是否为数字,同时还会检查是否存在未定义的变量,以避免出现警告:
sub is_integer {
   defined $_[0] && $_[0] =~ /^[+-]?\d+$/;
}

sub is_float {
   defined $_[0] && $_[0] =~ /^[+-]?\d+(\.\d+)?$/;
}

这里有一些阅读资料,你应该看一下。


2
我认为这有点偏离主题,特别是当提问者说“简单”时。许多情况,包括科学计数法,都不是很简单。除非将其用于模块,否则我不会担心这些细节。有时候简单就是最好的。不要把巧克力糖浆放进奶牛里制作巧克力牛奶! - osirisgothra
'.7' 可能是最简单的情况之一,但仍然容易被忽略... 最好尝试使用 /^[+-]?\d.?\d+$/ 来匹配浮点数。我的变体还考虑了科学计数法:/^[+-]?\d.?\d+(?:(?:e|E)\d+)?$/。 - Aconcagua
1
\d*\.?\d+ 部分存在 ReDoS 风险。我建议使用 /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?$//^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?(?:(?<=[\d.])e[+-]?\d+)?$/i 来包括科学计数法(解释和示例)。这使用了双重负向先行断言来防止像 ..e0 这样的字符串被认为是数字。它还使用了正向后行断言来确保 e 后面跟着一个数字。 - Adam Katz

10

$x 是否是数值类型的内容的一个简单(也许过于简化)的答案如下:

if ($x  eq  $x+0) { .... }

它对原始的$x和将$x转换为数字值后的值进行文本比较。


1
如果你使用"-w"或者"use warnings;",那么这将会抛出警告。 - Derek Park
1
警告可以通过移除$x eq (($x+0)."")来解决,然而更糟糕的问题是,在这个函数下,“1.0”不是数值。 - Eponymous
1
测试 $x+0 ne '' 已足够。当您测试 0001 时,正确的数字将被检查为非数字。同样的情况也适用于测试 '.05' 文本值。 - Znik
"$x eq $x+0" 对于 "0.0", ".5", "0x1" 等数值会失败。 - Skeeve

4

虽不是完美的方法,但您可以使用正则表达式:

sub isnumber 
{
    shift =~ /^-?\d+\.?\d*$/;
}

与andrewrk的答案相同的问题:错过了许多甚至简单的情况,例如'.7'。 - Aconcagua

2

正则表达式并非完美...原因如下:

use Try::Tiny;

sub is_numeric {
  my ($x) = @_;
  my $numeric = 1;
  try {
    use warnings FATAL => qw/numeric/;
    0 + $x;
  }
  catch {
    $numeric = 0;
  };
  return $numeric;
}

2

Regexp::Common中可以找到一个稍微健壮一些的正则表达式。

看起来你想知道Perl是否认为一个变量是数字。这里有一个函数可以捕获该警告:

sub is_number{
  my $n = shift;
  my $ret = 1;
  $SIG{"__WARN__"} = sub {$ret = 0};
  eval { my $x = $n + 1 };
  return $ret
}

另一个选择是在本地关闭警告:

{
  no warnings "numeric"; # Ignore "isn't numeric" warning
  ...                    # Use a variable that might not be numeric
}

请注意,非数字变量将被静默转换为0,这可能正是您想要的。

1

我觉得这很有趣。

if ( $value + 0 eq $value) {
    # A number
    push @args, $value;
} else {
    # A string
    push @args, "'$value'";
}

你需要解释得更好一些,你说你觉得这很有趣,但它是否回答了问题?试着解释一下为什么你的答案是问题的解决方案。 - Kumar Saurabh
例如,我的 $value 是 1,$value + 0 仍然是 1。与 $value 进行比较,1 等于 1。如果 $value 是一个字符串,比如 "swadhi",那么 $value + 0 就会变成字符串 "swadhi" 的 ASCII 值加上 0,得到另一个数字。 - Swadhikar
如果$value为'10.0',则该程序无法正常工作。 - soger

1

试试这个:

If (($x !~ /\D/) && ($x ne "")) { ... }

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