¿Cómo puedo saber si una variable tiene un valor numérico en Perl?

83

¿Existe una forma sencilla en Perl que me permita determinar si una variable dada es numérica? Algo parecido a:

if (is_number($x))
{ ... }

sería ideal. Sin -wduda, se prefiere una técnica que no arroje advertencias cuando se usa el interruptor.

Derek Park
fuente

Respuestas:

128

Use Scalar::Util::looks_like_number()que usa la función looks_like_number () de la API interna de Perl C, que es probablemente la forma más eficiente de hacer esto. Tenga en cuenta que las cadenas "inf" e "infinity" se tratan como números.

Ejemplo:

#!/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";
}

Da esta salida:

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

Ver también:

Sin sombrero
fuente
1
Y, como es habitual con los documentos de perl, es bastante difícil encontrar la definición real de lo que hace la función. Seguir el rastro perldoc perlapinos dice: Prueba si el contenido de un SV parece un número (o es un número). "Inf" e "Infinity" se tratan como números (por lo que no emitirán una advertencia no numérica), incluso si su atof () no los asimila. Difícilmente una especificación comprobable ...
Día
3
La descripción en Scalar :: Util está bien, looks_like_number le dice si su entrada es algo que Perl trataría como un número, que no es necesariamente la mejor respuesta para esta pregunta. La mención de atof es irrelevante, atof no es parte de CORE :: o POSIX (debería estar mirando strtod que ha subsumido atof y / es / parte de POSIX) y asumiendo que lo que Perl piensa que es un número es una entrada numérica válida a las funciones de C es obviamente muy incorrecto.
MkV
muy buena función :) para cadenas no definidas y no numéricas devuelve 0, para cadenas numéricas devuelve 1, para enteros devuelve 4352 y para flotantes devuelve 8704 :) generalmente> se detecta 0 número. Lo he probado en linux.
Znik
1
Me gusta esta función en general, pero considero grandes entradas. 1000000 es una gran cantidad de ceros para realizar un seguimiento, pidiendo un error, pero 1,000,000 se ve como una matriz de tres elementos, por lo que Perl acepta 1_000_000, pero looks_like_number () dice que no. Me pone triste.
Dave Jacoby
Nota: Las cadenas hexadecimales como 0x12se no considera los números de esta prueba.
Adam Katz
23

Consulte el módulo CPAN Regexp :: Common . Creo que hace exactamente lo que necesitas y maneja todos los casos extremos (por ejemplo, números reales, notación científica, etc.). p.ej

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

La pregunta original era cómo saber si una variable era numérica, no si "tiene un valor numérico".

Hay algunos operadores que tienen modos de operación separados para operandos numéricos y de cadena, donde "numérico" significa cualquier cosa que originalmente era un número o que alguna vez se usó en un contexto numérico (p. Ej. $x = "123"; 0+$x, En , antes de la adición, $xes una cadena, luego se considera numérico).

Una forma de saberlo es esta:

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

Si la función bit a bit está habilitada, eso hace que &solo sea un operador numérico y agrega un &.operador de cadena separado , debe deshabilitarlo:

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

(bit a bit está disponible en perl 5.022 y superior, y está habilitado de forma predeterminada si usted use 5.028;o superior).

ysth
fuente
¡Excelente gracias! Esto es precisamente lo que estaba buscando.
Juan A. Navarro
Si empaqueto su rutina en un sub, obtengo un comportamiento extraño en el sentido de que detecta valores no numéricos correctamente, hasta que pruebo el primer valor numérico, que también se detecta correctamente como verdadero, pero luego, todo lo demás a partir de ahí. también es cierto. Sin embargo, cuando pongo una evaluación alrededor de la parte de longitud (...), funciona bien todo el tiempo. ¿Alguna idea de lo que me estaba perdiendo? sub numeric { $obj = shift; no warnings "numeric"; return eval('length($obj & "")'); }
yogibimbi
@yogibimbi: está reutilizando la misma variable $ obj cada vez; prueba my $obj = shift;. ¿Por qué la evaluación?
ysth
Uy, mi mal, lo usé my $obj = shift, por supuesto, simplemente no lo transferí correctamente de mi código al comentario, lo edité un poco. Sin embargo, sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); }produce el mismo error. Por supuesto, tener una variable global clandestina explicaría el comportamiento, es exactamente lo que esperaría en ese caso, pero desafortunadamente, no es tan simple. Además, eso sería capturado por strict& warnings. Probé la evaluación en un intento bastante desesperado por deshacerme del error, y funcionó. Sin razonamientos más profundos, solo prueba y error.
yogibimbi
Compruébelo usted mismo: 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. Si pones un eval ('') alrededor de la longitud, la última impresión daría un 0, como debería. Imagínate.
yogibimbi
9

Una respuesta simple (y tal vez simplista) a la pregunta es que el contenido de $xnumérico es el siguiente:

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

Hace una comparación textual del original $xcon el $xconvertido a un valor numérico.

Peter Vanroose
fuente
1
Eso arrojará advertencias si usa "-w" o "use advertencias".
Derek Park
1
Las advertencias se pueden eliminar, $x eq (($x+0)."")sin embargo, un problema peor es que bajo esta función, "1.0" no es numérico
Eponymous
1
basta con probar $ x + 0 ne ''. cuando envíe un mensaje de texto con 0001, el número correcto se marcará como no número. lo mismo ocurre cuando probará el valor de texto '.05'.
Znik
8

Por lo general, la validación de números se realiza con expresiones regulares. Este código determinará si algo es numérico y también buscará variables indefinidas para no generar advertencias:

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

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

Aquí hay material de lectura que debería consultar.

andrewrk
fuente
2
Creo que esto se desvía un poco, especialmente cuando el autor de la pregunta dijo / simple /. Muchos casos, incluida la notación científica, no son sencillos. A menos que use esto para un módulo, no me preocuparía por esos detalles. A veces, la simplicidad es lo mejor. ¡No pongas el jarabe de chocolate en la vaca para hacer leche con chocolate!
osirisgothra
'.7' es probablemente uno de los casos más simples que aún se pierde ... mejor intente /^[+-]?\d*\.?\d+$/ para float. Mi variante, considerando también la notación científica: /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/
Aconcagua
La \d*\.?\d+parte introduce un riesgo de ReDoS . Recomiendo /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?$/o /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?(?:(?<=[\d.])e[+-]?\d+)?$/ial efecto de incluir la notación científica ( explicación y ejemplos ) en su lugar. Esto utiliza una búsqueda anticipada negativa duplicada para evitar que las cadenas como .y .e0pasen como números. También utiliza una mirada hacia atrás positiva para garantizar que esigue un número.
Adam Katz
3

No es perfecto, pero puedes usar una expresión regular:

sub isnumber 
{
    shift =~ /^-?\d+\.?\d*$/;
}
granjero
fuente
El mismo problema que la respuesta de andrewrk: pierde muchos casos incluso simples, por ejemplo, '.7'
Aconcagua
2

Se puede encontrar una expresión regular un poco más robusta en Regexp :: Common .

Parece que quiere saber si Perl cree que una variable es numérica. Aquí hay una función que atrapa esa advertencia:

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

Otra opción es desactivar la advertencia localmente:

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

Tenga en cuenta que las variables no numéricas se convertirán silenciosamente a 0, que es probablemente lo que deseaba de todos modos.

Jon Ericson
fuente
2

rexep no perfecto ... esto es:

use Try::Tiny;

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

Prueba esto:

If (($x !~ /\D/) && ($x ne "")) { ... }
Centros para el Control y la Prevención de Enfermedades
fuente
1

Aunque encontré esto interesante

if ( $value + 0 eq $value) {
    # A number
    push @args, $value;
} else {
    # A string
    push @args, "'$value'";
}
Swadhikar
fuente
necesitas explicar una mejor, estás diciendo que te parece interesante, pero ¿responde a la operación? Trate de explicar por qué su respuesta es la solución para la pregunta formulada
Kumar Saurabh
Por ejemplo, mi $ valor es 1, $ valor + 0 sigue siendo el mismo 1. Al comparar con $ valor 1 es igual a 1. Si el $ valor es una cadena, diga "swadhi", entonces $ valor + 0 se convierte en el valor ascii de la cadena "swadhi" + 0 = algún otro número.
Swadhikar
0

Personalmente, creo que el camino a seguir es confiar en el contexto interno de Perl para que la solución sea a prueba de balas. Una buena expresión regular podría coincidir con todos los valores numéricos válidos y ninguno de los no numéricos (o viceversa), pero como hay una forma de emplear la misma lógica que usa el intérprete, debería ser más seguro confiar en eso directamente.

Como tiendo a ejecutar mis scripts -w, tuve que combinar la idea de comparar el resultado de "valor más cero" con el valor original con el no warningsenfoque basado en @ysth:

do { 
    no warnings "numeric";
    if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}
zagrimsan
fuente
-1

if (definido $ x && $ x! ~ m / \ D /) {} o $ x = 0 if! $ x; si ($ x! ~ m / \ D /) {}

Esta es una ligera variación de la respuesta de Veekay, pero déjeme explicar mi razonamiento para el cambio.

La realización de una expresión regular en un valor indefinido provocará un mensaje de error y hará que el código salga en muchos, si no en la mayoría de los entornos. Probar si el valor está definido o establecer un caso predeterminado como hice en el ejemplo alternativo antes de ejecutar la expresión guardará, como mínimo, su registro de errores.

Jason Van Patten
fuente