Jak sprawdzić, czy zmienna ma wartość liczbową w Perlu?

84

Czy w Perlu jest prosty sposób, który pozwoli mi określić, czy dana zmienna jest numeryczna? Coś w rodzaju:

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

byłoby idealne. Z -wpewnością preferowana jest technika, która nie generuje ostrzeżeń, gdy używany jest przełącznik.

Derek Park
źródło

Odpowiedzi:

129

Użyj, Scalar::Util::looks_like_number()która używa funkcji look_like_number () wewnętrznego API Perl C, która jest prawdopodobnie najbardziej wydajnym sposobem na zrobienie tego. Zauważ, że ciągi „inf” i „nieskończoność” są traktowane jako liczby.

Przykład:

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

Daje to wyjście:

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

Zobacz też:

nohat
źródło
1
I jak zwykle w perl docs, znalezienie rzeczywistej definicji tego, co robi ta funkcja, jest raczej trudne. Ślad perldoc perlapimówi nam: Sprawdź, czy zawartość SV wygląda jak liczba (lub jest liczbą). "Inf" i "Infinity" są traktowane jako liczby (więc nie będą generować ostrzeżeń nienumerycznych), nawet jeśli atof () ich nie rozumie. Trudno testowalna specyfikacja ...
Dzień
3
Opis w Scalar :: Util jest w porządku, look_like_number mówi ci, czy twoje dane wejściowe są czymś, co Perl potraktowałby jako liczbę, co niekoniecznie jest najlepszą odpowiedzią na to pytanie. Wzmianka o atof jest nieistotna, atof nie jest częścią CORE :: lub POSIX (powinieneś patrzeć na strtod, który podjął atof i / jest / częścią POSIX) i zakładając, że to, co Perl uważa za liczbę, jest poprawnym wejściem numerycznym do funkcji C jest oczywiście bardzo błędne.
MkV
bardzo fajna funkcja :) dla ciągów undef i nieliczbowych zwraca 0, dla ciągów liczbowych zwraca 1, dla liczb całkowitych zwraca 4352 a dla liczb zmiennoprzecinkowych zwraca 8704 :) generalnie> 0 liczba jest wykrywana. Przetestowałem to pod Linuksem.
Znik
1
Generalnie podoba mi się ta funkcja, ale rozważ duże int. 1000000 to wiele zer, które trzeba śledzić, prosząc o błąd, ale 1000000 jest postrzegane jako tablica trzyelementowa, więc Perl akceptuje 1_000_000, ale wygląda_ jak_liczba () mówi nie. Smutno mi.
Dave Jacoby,
Uwaga: ciągi szesnastkowe, takie jak, nie0x12 są uważane za liczby w tym teście.
Adam Katz
23

Sprawdź moduł CPAN Regexp :: Common . Myślę, że robi dokładnie to, czego potrzebujesz i obsługuje wszystkie skrajne przypadki (np. Liczby rzeczywiste, notacja naukowa itp.). na przykład

use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }
naumcho
źródło
23

Pierwotne pytanie dotyczyło tego, jak stwierdzić, czy zmienna jest numeryczna, a nie czy „ma wartość liczbową”.

Istnieje kilka operatorów, które mają oddzielne tryby działania dla operandów numerycznych i łańcuchowych, gdzie „numeryczny” oznacza wszystko, co pierwotnie było liczbą lub było kiedykolwiek użyte w kontekście numerycznym (np. $x = "123"; 0+$xPrzed dodaniem $xjest ciągiem, a następnie jest uważany za numeryczny).

Można to stwierdzić na przykład:

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

Jeśli funkcja bitowa jest włączona, to tworzy &tylko operator numeryczny i dodaje oddzielny &.operator ciągu , musisz ją wyłączyć:

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

(bitowe jest dostępne w Perlu 5.022 i nowszych i domyślnie włączone, jeśli Ty use 5.028;lub powyżej).

ysth
źródło
Wspaniale, dziękuję! Właśnie tego szukałem.
Juan A. Navarro
Jeśli spakuję twoją procedurę w podrzędną, otrzymam dziwne zachowanie, ponieważ poprawnie wykrywa wartości nienumeryczne, dopóki nie wypróbuję pierwszej wartości liczbowej, która również jest poprawnie wykrywana jako prawda, ale potem wszystko inne od tego momentu jest również prawdą. Kiedy jednak umieszczam eval wokół części długości (...), jednak cały czas działa dobrze. Masz jakiś pomysł, czego mi brakowało? sub numeric { $obj = shift; no warnings "numeric"; return eval('length($obj & "")'); }
yogibimbi
@yogibimbi: za każdym razem ponownie używasz tej samej zmiennej $ obj; spróbuj my $obj = shift;. Dlaczego eval?
ysth
Ups, mój błąd, użyłem my $obj = shiftoczywiście, po prostu nie przeniosłem go poprawnie z mojego kodu do komentarza, trochę go zredagowałem. Jednak sub numeric { my $obj = shift; no warnings "numeric"; return length($obj & ""); }powoduje ten sam błąd. Oczywiście posiadanie tajnej zmiennej globalnej wyjaśniłoby zachowanie, dokładnie tego bym się spodziewał w tym przypadku, ale niestety nie jest to takie proste. Ponadto zostałby złapany przez strict& warnings. Wypróbowałem eval w dość desperackiej próbie pozbycia się błędu i zadziałało. Bez głębszego rozumowania, po prostu próba i błąd.
yogibimbi
Sprawdź to: 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. Jeśli umieścisz eval ('') wokół długości, ostatni wydruk da 0, tak jak powinien. Domyśl.
yogibimbi
9

Zwykle walidacja liczb odbywa się za pomocą wyrażeń regularnych. Ten kod określi, czy coś jest numeryczne, a także sprawdzi, czy nie ma niezdefiniowanych zmiennych, aby nie rzucać ostrzeżeń:

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

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

Oto kilka materiałów do czytania, którym powinieneś się przyjrzeć.

andrewrk
źródło
2
Myślę, że to trochę odbiegające od normy, zwłaszcza gdy pytający powiedział / proste /. Wiele przypadków, w tym notacja naukowa, nie jest prostych. O ile nie używałbym tego do modułu, nie martwiłbym się o takie szczegóły. Czasami prostota jest najlepsza. Nie podawaj krowie syropu czekoladowego, aby zrobić mleko czekoladowe!
osirisgothra
'.7' jest prawdopodobnie jednym z najprostszych przypadków, które wciąż są pomijane ... lepiej try /^[+-]?\d*\.?\d+$/ for float. Mój wariant, biorąc pod uwagę również notację naukową: /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/
Aconcagua
\d*\.?\d+Część wprowadza REDOS ryzyko. Zamiast tego zalecam /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?$/lub /^[+-]?(?!\.(?!\d)|$)\d*(?:\.\d*)?(?:(?<=[\d.])e[+-]?\d+)?$/idołączam notację naukową ( wyjaśnienie i przykłady ). Używa to podwójnego ujemnego wyprzedzenia, aby zapobiec przekazywaniu ciągów takich jak .i .e0jako liczb. Używa również pozytywnego lookbehind, aby upewnić się, że enastępuje liczba.
Adam Katz,
9

Prosty (a może uproszczone) Odpowiedź na pytanie jest treść $xnumeryczne jest następujący:

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

Wykonuje tekstowe porównanie oryginału $xz $xprzekonwertowaną na wartość liczbową.

Peter Vanroose
źródło
1
To wyrzuci ostrzeżenia, jeśli użyjesz "-w" lub "użyj ostrzeżeń;".
Derek Park
1
Ostrzeżenia można usunąć, $x eq (($x+0)."")jednak gorszym problemem jest to, że w tej funkcji wartość „1.0” nie jest numeryczna
tytułowy
1
wystarczy przetestować $ x + 0 ne ''. kiedy wyślesz SMS-a 0001, poprawny numer zostanie sprawdzony jako nie numer. to samo dotyczy testu wartości tekstowej „.05”.
Znik
4

Niezbyt idealne, ale możesz użyć wyrażenia regularnego:

sub isnumber 
{
    shift =~ /^-?\d+\.?\d*$/;
}
farmerchris
źródło
Ten sam problem, co odpowiedź andrewrka: pomija wiele, nawet prostych przypadków, np. „.7”
Aconcagua
2

Nieco bardziej solidne wyrażenie regularne można znaleźć w Regexp :: Common .

Wygląda na to, że chcesz wiedzieć, czy Perl uważa, że ​​zmienna jest numeryczna. Oto funkcja, która przechwytuje to ostrzeżenie:

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

Inną opcją jest lokalne wyłączenie ostrzeżenia:

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

Zwróć uwagę, że zmienne nienumeryczne zostaną po cichu przekonwertowane na 0, co i tak prawdopodobnie jest tym, czego chciałeś.

Jon Ericson
źródło
2

rexep nie jest doskonały ... to jest:

use Try::Tiny;

sub is_numeric {
  my ($x) = @_;
  my $numeric = 1;
  try {
    use warnings FATAL => qw/numeric/;
    0 + $x;
  }
  catch {
    $numeric = 0;
  };
  return $numeric;
}
frędzlami
źródło
1

Spróbuj tego:

If (($x !~ /\D/) && ($x ne "")) { ... }
CDC
źródło
1

Jednak wydało mi się to interesujące

if ( $value + 0 eq $value) {
    # A number
    push @args, $value;
} else {
    # A string
    push @args, "'$value'";
}
Swadhikar
źródło
Musisz lepiej wyjaśnić, mówisz, że uważasz to za interesujące, ale czy odpowiada op? Spróbuj wyjaśnić, dlaczego twoja odpowiedź jest odpowiedzią na zadane pytanie
Kumar Saurabh
Na przykład moja wartość $ wynosi 1, wartość $ + 0 pozostaje taka sama 1. Porównując z wartością $ 1 równa się 1. Jeśli wartość $ jest łańcuchem powiedz "swadhi" to $ wartość + 0 staje się wartością ascii łańcucha "swadhi" + 0 = inna liczba.
Swadhikar
0

Osobiście uważam, że najlepszym rozwiązaniem jest poleganie na wewnętrznym kontekście Perla, aby rozwiązanie było kuloodporne. Dobre wyrażenie regularne może pasować do wszystkich prawidłowych wartości liczbowych i żadnej z wartości nienumerycznych (lub odwrotnie), ale ponieważ istnieje sposób na zastosowanie tej samej logiki, której używa interpreter, bezpieczniej jest polegać na tym bezpośrednio.

Ponieważ zwykle uruchamiam moje skrypty -w, musiałem połączyć pomysł porównania wyniku „wartość plus zero” z oryginalną wartością z no warningspodejściem opartym na @ysth:

do { 
    no warnings "numeric";
    if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}
zagrimsan
źródło
-1

if (zdefiniowane $ x && $ x! ~ m / \ D /) {} lub $ x = 0 jeśli! $ x; if ($ x! ~ m / \ D /) {}

To niewielka odmiana odpowiedzi Veekaya, ale pozwól mi wyjaśnić moje uzasadnienie zmiany.

Wykonanie wyrażenia regularnego na niezdefiniowanej wartości spowoduje wyrzucenie błędu i spowoduje zamknięcie kodu w wielu, jeśli nie w większości środowisk. Testowanie, czy wartość jest zdefiniowana lub ustawienie przypadku domyślnego, tak jak w alternatywnym przykładzie, przed uruchomieniem wyrażenia spowoduje co najmniej zapisanie dziennika błędów.

Jason Van Patten
źródło