Is it a Number?

Is it a Number?

In innumerable postings and email the following question eternally arises:
How do I test a string to see if it's a number?
I hate this question.
I'm sure there's an obvious answer to this,
Why yes, there is. I generally like this way: if ($n == 0) { warn "icky number" }
but I haven't yet found or figured it out. Thanks...
Why do you want to see whether it's a number? Perl is happy to use strings and numbers interchangeably. People make way too much trouble of this. Normally, all you want to do is
    $answer += 0;
To perform this conversion, Perl merely calls your native C librarys's atof(3) function. Many important benefits come from this approach, which if you try to roll it yourself, you'll probably miss. Here are some of them:
  1. Your current LC_NUMERIC locale setting is used and respected. This means that the user's local radix will be respected. For example, if you're in a country where the USA's notion of comma and period are switched, the right thing will still happen.
  2. Exponent overflow and underflow produce the results specified by the IEEE Standard.
  3. The special IEEE notions of Infinity and NaN (not a number) will be properly honored:
    
      $n = 'NaN';
      print 2 * $n;
    NaN
      print 10 * $n
    NaN
      print 1 + NaN
    NaN
      $i = 'Infinity' 
      print 1 + $i
    Infinity
      print $i * $i
    Infinity
      print $i - $i
    NaN
      print 'Infinity' < 0
    0
      print 'Infinity' > 0
    1
    
You may also notice that NaN is neither == nor != 0, which is probably what you want. If you disagree with the way your vendor has implemented atof(3), then complain to them, but you'd better be up on your standards docs first. If you don't like that atof(3) tolerates trailing non-numerics, just cope.

Assuming you don't care about whether something's zero or has trailing garbage, some slightly simplistic solutions certainly suggest themselves:

do { print "Number, please: "; $answer = <STDIN>; print "Bad number\n" if $answer == 0; } until $answer; If you do care about getting 0's, then do this: do { print "Number, please: "; $answer = <STDIN>; if ($answer == 0 && $answer ne '0') { print "Bad number\n"; } } until $answer; A related approach is to see whether the lexical and numeric representations are the same. This solution is often by those who don't like trailing non-digits in their numbers: do { print "Number, please: "; $answer = <STDIN>; if ($answer+0 ne $answer) { print "Bad number\n"; } } until $answer;

If you find yourself unduly annoyed from being chidden about improper numeric conversions, as I'm sure I'm about to be, just do something like this:

do { print "Number, please: "; $answer = <STDIN>; local $^W = 0; if ($answer == 0 && $answer ne '0') { print "Bad number\n"; } } until $answer; If you want to wrap it in a function, do this: sub bogus_number { my $potential_number = shift; local $^W = 0; my $bogosity = $potential_number == 0 && $potential_number ne ''; return $bogosity; } Hm... one of these days we're going to have deal with this problem of maybe getting EOF. Remember you can't actually test for eof() explicitly, or you'll hose the interactive user. do { print "Number, please: "; exit unless defined ($answer = <STDIN>); if (bogus_number($answer)) { print "Bad number\n"; $answer = 0; } } until $answer; You could even be cruel and clobber their input: sub bogus_number { local $^W = 0; if ($_[0] == 0 && $_[0] ne '') { $_[0] = ''; # squish my caller! return 1; } return 0; } do { print "Number, please: "; $answer = <STDIN>; print "Bad number\n" if bogus_number($answer); } until $answer; Or write it the other way: do { print "Number, please: "; $answer = <STDIN>; } until nifty_number($answer); sub nifty_number { my $potential_number = shift; local $^W = 0; my $bogosity = $potential_number == 0 && $potential_number ne ''; return !$bogosity; } Someone is going to ask the question ``Can't I use a regular expression do this?'' Why, yes, Virginia, you may, and don't say can. :-) Actually, regular expressions are the general way one verifies input in Perl. Here are some simple-minded schemes for detecting such things: sub is_whole_number { $_[0] =~ /^\d+$/ } sub is_integer { $_[0] =~ /^[+-]?\d+$/ } sub is_float { $_[0] =~ /^[+-]?\d+\.?\d*$/ } For a more proper solution, chew on this output from an old paper-tape processing machine from Mark Biggar: sub nifty_number { $_[0] =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/; } or written out more legibly: sub nifty_number { $_[0] =~ m{ # YANETUT ^ ( [+-]? ) (?= \d | \.\d ) \d* ( \. \d* ) ? ( [Ee] ( [+-]? \d+ ) ) ? $ }x } Nearly all of these solutions suffer from problems that the simple $num += 0; has no problems with.
Return to:
Copyright 1996 Tom Christiansen.
All rights reserved.