Wie kann ich feststellen, ob eine Variable in Perl einen numerischen Wert hat?

83

Gibt es eine einfache Möglichkeit in Perl, mit der ich feststellen kann, ob eine bestimmte Variable numerisch ist? Etwas in der Art von:

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

wäre ideal. Eine Technik, die bei Verwendung des -wSchalters keine Warnungen auslöst, wird sicherlich bevorzugt.

Derek Park
quelle

Antworten:

128

Verwenden Sie Scalar::Util::looks_like_number()diese Option, um die Funktion look_like_number () der internen Perl C-API zu verwenden. Dies ist wahrscheinlich der effizienteste Weg, dies zu tun. Beachten Sie, dass die Zeichenfolgen "inf" und "unendlich" als Zahlen behandelt werden.

Beispiel:

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

Gibt diese Ausgabe:

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

Siehe auch:

nichts
quelle
1
Und wie bei Perl-Dokumenten üblich, ist es ziemlich schwierig , die tatsächliche Definition der Funktionsweise zu finden. perldoc perlapiWenn Sie dem Pfad folgen, erfahren Sie: Testen Sie, ob der Inhalt eines SV wie eine Zahl aussieht (oder eine Zahl ist). "Inf" und "Infinity" werden als Zahlen behandelt (es wird also keine nicht numerische Warnung ausgegeben), auch wenn Ihr atof () sie nicht erfasst. Kaum eine testbare Spezifikation ...
Tag
3
Die Beschreibung in Scalar :: Util ist in Ordnung. Looks_like_number sagt Ihnen, ob Ihre Eingabe etwas ist, das Perl als Zahl behandeln würde. Dies ist nicht unbedingt die beste Antwort auf diese Frage. Die Erwähnung von atof ist irrelevant, atof ist nicht Teil von CORE :: oder POSIX (Sie sollten sich strtod ansehen, das atof subsumiert hat und / oder Teil von POSIX ist) und davon ausgehen, dass das, was Perl für eine Zahl hält, eine gültige numerische Eingabe ist zu C Funktionen ist offensichtlich sehr falsch.
MkV
Sehr schöne Funktion :) Für undef und Nicht-Zahlen-Strings wird 0 zurückgegeben, für Zahlen-Strings wird 1 zurückgegeben, für Ganzzahlen werden 4352 zurückgegeben und für Floats werden 8704 zurückgegeben :) Im Allgemeinen wird eine Zahl> 0 erkannt. Ich habe es unter Linux getestet.
Znik
1
Ich mag diese Funktion im Allgemeinen, betrachte aber große Ints. 1000000 ist eine Menge Nullen, die nachverfolgt werden müssen, um Fehler zu bitten, aber 1.000.000 werden als Array mit drei Elementen angesehen, sodass Perl 1_000_000 akzeptiert, aber look_like_number () nein sagt. Das macht mich traurig.
Dave Jacoby
Hinweis: Hexadezimale Zeichenfolgen wie 0x12werden bei diesem Test nicht als Zahlen betrachtet.
Adam Katz
23

Schauen Sie sich das CPAN-Modul Regexp :: Common an . Ich denke, es macht genau das, was Sie brauchen, und behandelt alle Randfälle (z. B. reelle Zahlen, wissenschaftliche Notation usw.). z.B

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

Die ursprüngliche Frage war, wie man erkennt, ob eine Variable numerisch ist, nicht, ob sie "einen numerischen Wert hat".

Es gibt einige Operatoren, die getrennte Betriebsmodi für numerische und Zeichenfolgenoperanden haben, wobei "numerisch" alles bedeutet, was ursprünglich eine Zahl war oder jemals in einem numerischen Kontext verwendet wurde (z. B. in $x = "123"; 0+$x, bevor die Addition $xeine Zeichenfolge ist, danach wird als numerisch betrachtet).

Eine Möglichkeit zu sagen ist folgende:

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

Wenn die bitweise Funktion aktiviert ist, die &nur einen numerischen Operator erstellt und einen separaten Zeichenfolgenoperator hinzufügt &., müssen Sie sie deaktivieren:

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

(bitweise ist in Perl 5.022 und höher verfügbar und standardmäßig aktiviert, wenn Sie use 5.028;oder höher.)

ysth
quelle
Ausgezeichnet, vielen Dank! Genau das habe ich gesucht.
Juan A. Navarro
Wenn ich Ihre Routine in ein Sub packe, bekomme ich ein seltsames Verhalten, indem es nicht numerische Werte korrekt erkennt, bis ich den ersten numerischen Wert ausprobiere, der ebenfalls korrekt als wahr erkannt wird, aber dann alles andere von da an heraus ist auch wahr. Wenn ich eine Bewertung um den Längesteil (...) lege, funktioniert dies jedoch immer gut. Irgendeine Idee, was mir fehlte? sub numeric { $obj = shift; no warnings "numeric"; return eval('length($obj & "")'); }
Yogibimbi
@yogibimbi: Sie verwenden jedes Mal dieselbe Variable $ obj. versuche es my $obj = shift;. Warum die Bewertung?
ysth
oops, mein schlechtes, ich habe verwendet my $obj = shift natürlich verwendet, habe es einfach nicht richtig von meinem Code auf den Kommentar übertragen, ich habe es ein bisschen bearbeitet. Allerdings sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); }erzeugt die gleichen Fehler. Natürlich würde eine geheime globale Variable das Verhalten erklären, genau das würde ich in diesem Fall erwarten, aber leider ist es nicht so einfach. Auch das würde von strict& gefangen werden warnings. Ich habe die Auswertung in einem ziemlich verzweifelten Versuch versucht, den Fehler zu beseitigen, und es hat funktioniert. Keine tieferen Überlegungen, nur Versuch und Irrtum.
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. Wenn Sie eine Auswertung ('') um die Länge setzen, ergibt der letzte Ausdruck eine 0, wie es sollte. Stelle dir das vor.
Yogibimbi
9

Eine einfache (und vielleicht vereinfachende) Antwort auf die Frage lautet: Der Inhalt der $xnumerischen lautet wie folgt:

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

Es wird ein Textvergleich des Originals $xmit dem $xin einen numerischen Wert konvertierten Wert durchgeführt.

Peter Vanroose
quelle
Das wird Warnungen auslösen, wenn Sie "-w" oder "use warnings;" verwenden.
Derek Park
1
Die Warnungen können entfernt werden, $x eq (($x+0)."")ein schlimmeres Problem ist jedoch, dass unter dieser Funktion "1.0" nicht numerisch ist
gleichnamig
1
es reicht aus, $ x + 0 ne '' zu testen. Wenn Sie 0001 schreiben, wird die richtige Nummer als Nicht-Nummer überprüft. Das gleiche gilt, wenn Sie den Textwert '.05' testen.
Znik
8

Normalerweise erfolgt die Validierung von Zahlen mit regulären Ausdrücken. Dieser Code bestimmt, ob etwas numerisch ist, und sucht nach undefinierten Variablen, um keine Warnungen auszulösen:

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

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

Hier ist etwas Lesematerial, das Sie sich ansehen sollten.

andrewrk
quelle
2
Ich denke, das schweift ein bisschen ab, besonders wenn der Fragesteller / simple / sagte. Viele Fälle, einschließlich der wissenschaftlichen Notation, sind kaum einfach. Wenn ich dies nicht für ein Modul verwende, würde ich mir über solche Details keine Sorgen machen. Manchmal ist Einfachheit am besten. Geben Sie den Schokoladensirup nicht in die Kuh, um Schokoladenmilch herzustellen!
Osirisgothra
'.7' ist wahrscheinlich einer der einfachsten Fälle, die immer noch übersehen werden ... versuchen Sie es besser mit /^[+-‹?\d*\.?\d+$/ für float. Meine Variante, wissenschaftliche Notation unter Berücksichtigung auch: /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/
Aconcagua
Der \d*\.?\d+Teil führt ein ReDoS- Risiko ein. Ich empfehle /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?$/oder füge stattdessen /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?(?:(?<=[\d.])e[+-]?\d+)?$/ieine wissenschaftliche Notation ( Erklärung und Beispiele ) hinzu. Dies verwendet einen doppelt negativen Lookahead, um zu verhindern, dass Zeichenfolgen wie .und .e0als Zahlen übergeben werden. Es wird auch ein positives Aussehen verwendet, um sicherzustellen, dass eeine Zahl folgt.
Adam Katz
3

Nicht perfekt, aber Sie können einen regulären Ausdruck verwenden:

sub isnumber 
{
    shift =~ /^-?\d+\.?\d*$/;
}
Bauerchris
quelle
Gleiches Problem wie Andrewrks Antwort: Es fehlen viele einfache Fälle, z. B. '.7'
Aconcagua
2

Eine etwas robustere Regex finden Sie in Regexp :: Common .

Es hört sich so an, als ob Sie wissen möchten, ob Perl eine Variable für numerisch hält. Hier ist eine Funktion, die diese Warnung abfängt:

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

Eine andere Möglichkeit besteht darin, die Warnung lokal auszuschalten:

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

Beachten Sie, dass nicht numerische Variablen stillschweigend in 0 konvertiert werden, was wahrscheinlich sowieso das ist, was Sie wollten.

Jon Ericson
quelle
2

rexep nicht perfekt ... das ist:

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
quelle
1

Versuche dies:

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

Ich fand das allerdings interessant

if ( $value + 0 eq $value) {
    # A number
    push @args, $value;
} else {
    # A string
    push @args, "'$value'";
}
Swadhikar
quelle
Sie müssen ein besseres erklären, Sie sagen, Sie finden es interessant, aber beantwortet es die Operation? Versuchen Sie zu erklären, warum Ihre Antwort die Lösung für die gestellte Frage ist
Kumar Saurabh
Zum Beispiel ist mein $ -Wert 1, $ value + 0 bleibt gleich 1. Durch Vergleichen mit $ value 1 ist 1 gleich 1. Wenn der $ -Wert eine Zeichenfolge ist, die "swadhi" sagt, wird $ value + 0 zum ASCII-Wert der Zeichenfolge "swadhi". + 0 = eine andere Zahl.
Swadhikar
0

Persönlich denke ich, dass der Weg dahin darin besteht, sich auf den internen Kontext von Perl zu verlassen, um die Lösung kugelsicher zu machen. Ein guter regulärer Ausdruck könnte mit allen gültigen numerischen Werten und keinem der nicht numerischen Werte übereinstimmen (oder umgekehrt). Da es jedoch eine Möglichkeit gibt, dieselbe Logik zu verwenden, die der Interpreter verwendet, sollte es sicherer sein, sich direkt darauf zu verlassen.

Da neige ich dazu, meine Skripte mit auszuführen -w , musste ich die Idee, das Ergebnis von "Wert plus Null" mit dem ursprünglichen Wert zu vergleichen, mit dem no warningsbasierten Ansatz von @ysth kombinieren:

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

if (definiert $ x && $ x! ~ m / \ D /) {} oder $ x = 0 if! $ x; if ($ x! ~ m / \ D /) {}

Dies ist eine geringfügige Abweichung von Veekays Antwort, aber lassen Sie mich meine Gründe für die Änderung erläutern.

Das Ausführen eines regulären Ausdrucks für einen undefinierten Wert führt zu Fehlern und führt dazu, dass der Code in vielen, wenn nicht in den meisten Umgebungen beendet wird. Wenn Sie testen, ob der Wert definiert ist, oder einen Standardfall festlegen, wie ich es im alternativen Beispiel getan habe, bevor Sie den Ausdruck ausführen, wird mindestens Ihr Fehlerprotokoll gespeichert.

Jason Van Patten
quelle