Comment savoir si une variable a une valeur numérique en Perl?

83

Existe-t-il un moyen simple en Perl qui me permette de déterminer si une variable donnée est numérique? Quelque chose du genre:

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

serait idéal. Une technique qui ne lancera pas d'avertissements lorsque le -wcommutateur est utilisé est certainement préférée.

Parc Derek
la source

Réponses:

128

Utilisez Scalar::Util::looks_like_number()qui utilise la fonction looks_like_number () de l'API Perl C interne, ce qui est probablement le moyen le plus efficace de le faire. Notez que les chaînes "inf" et "infinity" sont traitées comme des nombres.

Exemple:

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

Donne cette sortie:

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

Voir également:

nohat
la source
1
Et comme d'habitude avec les documents Perl, trouver la définition réelle de ce que fait la fonction est assez difficile. Suivre la piste perldoc perlapinous dit: Teste si le contenu d'un SV ressemble à un nombre (ou est un nombre). "Inf" et "Infinity" sont traités comme des nombres (donc n'émettra pas d'avertissement non numérique), même si votre atof () ne les fait pas. À peine une spécification testable ...
Jour
3
La description dans Scalar :: Util est bonne, looks_like_number vous indique si votre entrée est quelque chose que Perl traiterait comme un nombre, ce qui n'est pas nécessairement la meilleure réponse à cette question. La mention de atof n'est pas pertinente, atof ne fait pas partie de CORE :: ou POSIX (vous devriez regarder strtod qui a subsumé atof et / est / fait partie de POSIX) et en supposant que ce que Perl pense être un nombre est une entrée numérique valide aux fonctions C est évidemment très faux.
MkV
très belle fonction :) pour les chaînes undef et non numériques renvoie 0, pour les chaînes numériques retourne 1, pour les entiers renvoie 4352 et pour les flottants renvoie 8704 :) généralement> 0 nombre est détecté. Je l'ai testé sous Linux.
Znik
1
J'aime cette fonction en général, mais je considère les gros ints. 1000000, c'est beaucoup de zéros à suivre, implorant une erreur, mais 1 000 000 est considéré comme un tableau à trois éléments, donc Perl accepte 1_000_000, mais looks_like_number () dit non. Me rend triste.
Dave Jacoby
Remarque: les chaînes hexadécimales comme ne0x12 sont pas considérées comme des nombres par ce test.
Adam Katz
23

Consultez le module CPAN Regexp :: Common . Je pense qu'il fait exactement ce dont vous avez besoin et gère tous les cas extrêmes (par exemple les nombres réels, la notation scientifique, etc.). par exemple

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

La question initiale était de savoir comment savoir si une variable était numérique, et non si elle "a une valeur numérique".

Il existe quelques opérateurs qui ont des modes de fonctionnement séparés pour les opérandes numériques et de chaîne, où "numérique" signifie tout ce qui était à l'origine un nombre ou qui a déjà été utilisé dans un contexte numérique (par exemple $x = "123"; 0+$x, avant l'addition, il $xy a une chaîne, après est considéré comme numérique).

Une façon de le dire est la suivante:

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

Si la fonctionnalité au niveau du bit est activée, cela crée &uniquement un opérateur numérique et ajoute un &.opérateur de chaîne distinct , vous devez la désactiver:

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

(au niveau du bit est disponible dans perl 5.022 et au-dessus, et activé par défaut si vous use 5.028;ou au-dessus.)

ysth
la source
Excellent merci! C'est précisément ce que je recherchais.
Juan A. Navarro
Si j'emballe votre routine dans un sous-marin, j'obtiens un comportement étrange en ce sens qu'il détecte correctement les valeurs non numériques, jusqu'à ce que j'essaye la première valeur numérique, qui est également détectée correctement comme vraie, mais ensuite, tout le reste à partir de là est également vrai. Quand je mets une évaluation autour de la partie de longueur (...), cependant, cela fonctionne bien tout le temps. Une idée de ce qui me manquait? sub numeric { $obj = shift; no warnings "numeric"; return eval('length($obj & "")'); }
yogibimbi
@yogibimbi: vous réutilisez à chaque fois la même variable $ obj; essayez my $obj = shift;. Pourquoi l'eval?
ysth
oups, mon mauvais, je l'ai utilisé my $obj = shift, bien sûr, juste ne pas le transférer correctement de mon code au commentaire, je l'ai un peu édité. Cependant, sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); }produit la même erreur. Bien sûr, avoir une variable globale clandestine expliquerait le comportement, c'est exactement ce à quoi je m'attendrais dans ce cas, mais malheureusement, ce n'est pas si simple. De plus, cela serait capturé par strict& warnings. J'ai essayé l'évaluation dans une tentative plutôt désespérée de me débarrasser de l'erreur, et cela a fonctionné. Pas de raisonnement plus profond, juste des essais et des erreurs.
yogibimbi
Check it out: 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 vous mettez un eval ('') autour de la longueur, la dernière impression donnerait un 0, comme il se doit. Allez comprendre.
yogibimbi
9

Une réponse simple (et peut-être simpliste) à la question est que le contenu de $xnumérique est le suivant:

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

Il effectue une comparaison textuelle de l'original $xavec le $xconverti en une valeur numérique.

Peter Vanroose
la source
1
Cela lancera des avertissements si vous utilisez "-w" ou "use warnings;".
Derek Park
1
Les avertissements peuvent être supprimés, $x eq (($x+0)."")mais le pire problème est que sous cette fonction, "1.0" n'est pas numérique
Eponyme
1
il suffit de tester $ x + 0 ne ''. lorsque vous enverrez un texto 0001, le numéro correct sera vérifié comme non-nombre. la même chose est lorsque vous testerez la valeur de texte «.05».
Znik
8

La validation des nombres est généralement effectuée avec des expressions régulières. Ce code déterminera si quelque chose est numérique et vérifiera les variables non définies pour ne pas lancer d'avertissements:

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

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

Voici quelques lectures que vous devriez consulter.

andrewrk
la source
2
Je pense que cela s'écarte un peu, surtout quand le demandeur a dit / simple /. De nombreux cas, y compris la notation scientifique, ne sont guère simples. À moins de l'utiliser pour un module, je ne m'inquiéterais pas de ces détails. Parfois, la simplicité est la meilleure. Ne mettez pas le sirop de chocolat dans la vache pour faire du lait au chocolat!
osirisgothra
'.7' est probablement l'un des cas les plus simples qui manque encore ... mieux vaut essayer /^[+- </font>?\d*\.?\d+$/ pour float. Ma variante, compte tenu de la notation scientifique, aussi: /^[+
Aconcagua
La \d*\.?\d+partie introduit un risque ReDoS . Je recommande /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?$/ou /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?(?:(?<=[\d.])e[+-]?\d+)?$/id'inclure la notation scientifique ( explication et exemples ) à la place. Cela utilise une anticipation négative doublée pour empêcher également les chaînes comme .et .e0de passer sous forme de nombres. Il utilise également un regard positif en arrière pour s'assurer que le esuit un nombre.
Adam Katz
3

Pas parfait, mais vous pouvez utiliser une regex:

sub isnumber 
{
    shift =~ /^-?\d+\.?\d*$/;
}
agriculteur
la source
Même problème que la réponse d'Andrewrk: manque de nombreux cas, même simples, par exemple '.7'
Aconcagua
2

Une regex légèrement plus robuste peut être trouvée dans Regexp :: Common .

Il semble que vous vouliez savoir si Perl pense qu'une variable est numérique. Voici une fonction qui intercepte cet avertissement:

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

Une autre option consiste à désactiver l'avertissement localement:

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

Notez que les variables non numériques seront silencieusement converties en 0, ce qui est probablement ce que vous vouliez de toute façon.

Jon Ericson
la source
2

rexep pas parfait ... c'est:

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
la source
1

Essaye ça:

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

J'ai trouvé ça intéressant

if ( $value + 0 eq $value) {
    # A number
    push @args, $value;
} else {
    # A string
    push @args, "'$value'";
}
Swadhikar
la source
vous devez mieux expliquer, vous dites que vous trouvez cela intéressant mais est-ce que cela répond à l'op? Essayez d'expliquer pourquoi votre réponse est la solution à la question posée
Kumar Saurabh
Par exemple, ma $ value est 1, $ value + 0 reste la même 1. En comparant avec $ value 1 vaut 1. Si la $ value est une chaîne, dites "swadhi" alors $ value + 0 devient la valeur ascii de la chaîne "swadhi" + 0 = un autre nombre.
Swadhikar
0

Personnellement, je pense que la voie à suivre est de s'appuyer sur le contexte interne de Perl pour rendre la solution à toute épreuve. Une bonne expression rationnelle pourrait correspondre à toutes les valeurs numériques valides et aucune des valeurs non numériques (ou vice versa), mais comme il existe un moyen d'utiliser la même logique que l'interpréteur utilise, il devrait être plus sûr de s'appuyer directement sur cela.

Comme j'ai tendance à exécuter mes scripts avec -w, j'ai dû combiner l'idée de comparer le résultat de "valeur plus zéro" à la valeur d'origine avec l' no warningsapproche basée sur @ysth:

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

if (défini $ x && $ x! ~ m / \ D /) {} ou $ x = 0 if! $ x; si ($ x! ~ m / \ D /) {}

Il s'agit d'une légère variation de la réponse de Veekay, mais laissez-moi vous expliquer mon raisonnement pour le changement.

L'exécution d'une expression régulière sur une valeur indéfinie provoquera un spew d'erreur et entraînera la fermeture du code dans de nombreux, sinon la plupart des environnements. Tester si la valeur est définie ou définir un cas par défaut comme je l'ai fait dans l'exemple alternatif avant d'exécuter l'expression enregistrera au minimum votre journal des erreurs.

Jason Van Patten
la source