http://qs321.pair.com?node_id=408607

rrwo has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to come up with some code to detect if a scalar variable contains a number or string. The difficulty is that I want to detect objects which have the appropriate conversion and comparison operators overloaded.

The reason is that it's common to insist that certain values be strings or numbers, but checks for them that rule out references will rule out objects that have numberlike or stringlike behaviour (such as BigInts or custom classes). So I want to know that references (blessed things) refer to objects which act just like numbers or strings.

This turns out to be not-so-simple for the case of numbers.

According to the perlfunc manpage on sort, I should be able to use <=> to compare things, and if it's NaN, it turns out undefined. I think the page is outdated. The following turns out true in ActiveState Perl Build 810 (Perl 5.8.4):

$a = 'string'; if (defined ($a <=> $a)) { print "Oops"; }

The == operator also seems to work for NaNs (Not-a-Numbers) when it's not supposed to.

I do know there's been discussions/complaints in the past about how bad it is when Perl distinquishes between == and eq (and found this frustrating when I was a Perl newbie...) but the docs should have been updated if this was changed. Was it changed???

Furthermore, I seem to have run into the following hiccup from overload:

package Foo; use overload '0+' => \&as_num; sub new { my $class = shift; my $self = { value => shift }; bless $self, $class; } sub as_num { my $self = shift; return $self->{value}; } package main; my $x = new Foo(3); my $z = (0+$x); # $x+0 also produces error

I run this and get the following error: "Operation `+': no method found, left argument has no overloaded magic, right argument in overloaded package Foo at bug.pl line 22."

This is based on a section from the cookbook in the overload manpage!

So my attempts to detect if 0+$num do not work.

To add to this, it seems that the looks_like_number function in the Scalar::Util module is broken, in that it says some references are numbers:

use Scalar::Util 'looks_like_number'; my $a = [ ]; my $h = { }; print looks_like_number($a), "\n"; print looks_like_number($h), "\n";

The code I was experimenting with was the following:

sub _numberlike { return 1, unless defined $_[0]; # L<perlfunc> manpage notes that NaN != NaN, so we can verify that # numeric conversion function works properly along with the # comparison operator. no warnings; return 1 if ((!ref $_[0]) || blessed($_[0])) && eval { ((0+$_[0]) == (0+$_[0])) && (($_[0] <=> $_[0])==0) }; return; }

In case you're interested, the "stringlike" function I have (which seems to work) is:

sub _stringlike { return 1 unless defined $_[0] && ref $_[0]; return 1 if (blessed $_[0]) && eval { (("$_[0]" eq "$_[0]") && (($_[0] cmp $_[0])==0)) }; return; }

To explain how these work, I am checking that the appropriate number/strong conversions work, and that the comparison operators work. Overload is supposed to create the less than/greater than variations from the comparison operators. For my purposes, these are all that I need to check for.

Also.... you might recognize this issue from some posts of mine on the module-authors list.

Replies are listed 'Best First'.
Re: Detecting if a scalar has a number or string
by Thelonious (Scribe) on Nov 18, 2004 at 00:54 UTC
    About the 0+ issue, 0+ is only used in a boolean test, like while or ?:. In your code, you're actually using the + operator. You're just using it to add 0 to your var. It's funny about Scalar::Util. I get the same thing. I can't wait to hear a reason... You might try DBI::looks_like_number(), which does something I don't know what, but it looks cool! (I'm pretty sure that it checks for whether a var is currently being considered a number or a string. Best,
Re: Detecting if a scalar has a number or string
by water (Deacon) on Nov 18, 2004 at 02:23 UTC
    # untested! use Regexp::Common; sub isnumeric { my ($x) = @_; # force stringification, if $x is an object. my $s = "$x"; # determine if numberish. roll-your-own or i prefer CPAN. return $s =~ /$RE{num}{real}/; }
    of course, the assumption above is that an object that stringifies to a number will support other number-like operations, like == or  + , -, * etc.

    if you can't make that assumption, then you'd need to test if the scalar is an object, and if so, use it in various math contexts w/in an eval loop to see if the object supports the overloaded math ops you need. ack.

    even that is an assumption -- let's say the object doesn't throw an exception when  +=0 or  *=1. even in this case, you don't know the overloaded op is doing what you think: the object may have overloaded * to mean convolution or scalar product or who-knows-what, rather than common multiplication.

    so....

    it depends on how much you can assume. in most cases, i'd be comfortable assuming that if an object stringifies to something numberlike, it's worth trying to use it as a number.

    and  eval is your friend, so if you assume wrong, you can trap and respond gracefully.

    water

Re: Detecting if a scalar has a number or string
by mojotoad (Monsignor) on Nov 17, 2004 at 22:35 UTC
    Can you count on stringification to work? So with your overloaded object can you do something like _numberlike("$ref") ?

    Matt

      Counting on stringification is an interesting idea, but not reliable. Suppose I have a roman numerals class that behaves just like numeric variables, but outputs as roman numerals in string context?

Re: Detecting if a scalar has a number or string
by dragonchild (Archbishop) on Nov 18, 2004 at 13:08 UTC
    So, what you're looking for is the mythical can_usefully_act_as_a_number() function. Riiiight.

    The problem isn't a Perl issue. There is some very simple XS code that determines if the internal SV contains a value in the number slot. (I think this is what DBI does in its "Is it a number?" function.)

    The problem is actually a semantic one. What do you consider to be a useful number? For example, why do you think Scalar::Util's function is called "looks_like_number" instead of "is_number"? It's because what one person considers a number, another person won't.

    Why don't you describe what it is that determines if something can usefully act as a number? I think you'll find it a lot more difficult than you realize. And, without the requirements, writing the code is a little more difficult.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      You've completely missed the point of my post! I don't care about semantics, I care about whether the numeric operators (specifically the comparison operators for my needs) are defined. Either it's a native Perl number or it's an overloaded object (such as a BigInt). If it acts just like a number as far as Perl is concerned, then it's a number as far as I'm concerned.

        (Go back and read my reply more carefully, specifically the last paragraph.)

        So, you want only those things that are either numeric scalars or which inherit from overload and can provide the numeric comparisons. That's a good definition of what you feel a number is.

        To achieve that, you will probably want to do something along these lines:

        use Scalar::Utils qw( blessed ); use DBI qw( looks_like_number ); use overload; sub my_is_number { my $x = shift; unless ( blessed( $x ) ) { return looks_like_number( $x ); } if ( overload::Method( $x, '<=>' ) ) { return !!1; } return; }

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        If it acts just like a number as far as Perl is concerned, then it's a number as far as I'm concerned.

        You can let Perl tell you what it thinks then...

        #!perl use strict; use warnings; sub seems_like_number { my $thing = shift; use warnings qw/FATAL all/; # Promote warnings to fatal, so # they can be trapped. The effect is # lexically scoped. eval { $thing += 0; }; return $@ ? 0 : 1; } while ( <DATA> ){ chomp; if( seems_like_number( $_ ) ) { printf "%10s seems like a number.\n", $_; } else { printf "%10s doesn't seem like a number.\n", $_; } } __DATA__ 1234 1.234 -2.345 1-1+2 0.032 .321 ASDF ASDF1234 1234ASDF

        The first line after __DATA__ is intentionally blank, to test whether or not an empty string will qualify as a number... and of course it won't.

        Ok, now if Perl thinks it's not a number, you'll know about it, and if Perl doesn't object to it being considered a number, you'll know that too.


        Dave

Re: Detecting if a scalar has a number or string
by v_o_i_d (Novice) on Nov 18, 2004 at 07:16 UTC
    I run this and get the following error: "Operation `+': no method found, left argument has no overloaded magic, right argument in overloaded package Foo at bug.pl line 22."

    From memory, if that is a warning you can solve it by putting in the pragma no warnings 'void'; (or somethin like that). That will get rid of those pesky warnings. These pragmas should help you diagnose the problem:

    use strict; use warnings;

      It's an error, not a warning.

Re: Detecting if a scalar has a number or string
by ysth (Canon) on Nov 18, 2004 at 21:15 UTC

    According to the perlfunc manpage on sort, I should be able to use <=> to compare things, and if it's NaN, it turns out undefined. I think the page is outdated. The following turns out true in ActiveState Perl Build 810 (Perl 5.8.4):

    $a = 'string'; if (defined ($a <=> $a)) { print "Oops"; }

    The == operator also seems to work for NaNs (Not-a-Numbers) when it's not supposed to.

    I think you are under a misapprehension. $a there is not a NaN; a NaN is a floating point value that doesn't correspond to an actual number. On systems that support NaNs and infinite numbers, there are various rules about when a math operation will return one of these types. Try:
    my $Inf = 9**9**9; my $NaN = sin($Inf); print "inf is: $Inf\n", "NaN is: $NaN\n";
    Note: these are not strings; setting $a = "Inf" is not the same as setting it to an Inf floating point value.
Re: Detecting if a scalar has a number or string
by theorbtwo (Prior) on Nov 22, 2004 at 13:09 UTC

    You seem to have quite a number of issues here. The most important one that's keeping you from getting where you want is that your actuall wishes and your statement of your wishes don't match. You asked for a method "...that references (blessed things) refer to objects which act just like numbers or strings."

    Then you get that "asdf" is a valid number, and call it a problem. That's what you asked for: perl -le 'print "asdf"+42;'.

    Then, you make a mad rush though different bits of perl, and point out problems, without an understanding of the features, and why the problems aren't problems. You're misenterpreting the stuff about NaN and perl's arithmetic. That isn't refering to strings (since all strings are numbers -- some are just more numeric numbers then others) -- it's refering to a special sort of thing, which you get out of some sorts of strange math, but isn't a number -- generally, things that mathematicians prefer to call "undefined forms". perl -le 'print sin(9**9**9)' is a good example of a nan -- the sine of infinity is undefined. A NaN is, indeed, not equal to any other thing, even another NaN: <perl -le 'print sin(9**9**9)==sin(9**9**9)'

    The problem you're having with overload is simple. The error message says almost everything you need to know. Read the defintion of fallback in overload as well. Figure it out yet? Your class doesn't specify any way of handling +. Not only that, it doesn't tell overload to try to autogenerate a way to handle it (using 0+ and normal addition). Try adding fallback=>1 in your use overload line. (Note: To be fair, I'm not clear on why it doesn't work under fallback=undef mode either.)

    Your problem with looks_like_number is another one where you don't take into account perl's normal rules for numberhood. All references are numbers. Not only that, they're all nonzero numbers. perl -le 'my $a=[]; print "".$a, " == ", 0+$a;'.

    So, what is it you really want? You want a function, is_numeric, taking a single argument. If that argument is undef, then I assume it's not supposed to be considered numeric:

    sub is_numeric { local $_=shift; return if not defined $_; }
    If it's not a reference, then Scalar::Util::looks_like_number is good.
    use Scalar::Util 'looks_like_number'; sub is_numeric { local $_=shift; if (!ref $_) { return looks_like_number($_); } }
    Unblessed references are not numbers (says you).
    use Scalar::Util 'looks_like_number', 'blessed'; sub is_numeric { local $_=shift; if (!ref $_) { return looks_like_number($_); } if (!blessed $_) { return; } }
    References into packages that don't use overload are not numbers (says you).
    use Scalar::Util 'looks_like_number', 'blessed'; require overload; sub is_numeric { local $_=shift; if (!ref $_) { return looks_like_number($_); } if (!blessed $_) { return; } if (!overload::Overloaded $_) { return; } }
    Now, you have to figure out somehow if a package which does implement overloading is numeric or not... but you haven't given a definition anywhere that I can see as to what that means. That's quite a problem... because the point of overload is to make things that don't act quite like numbers or quite like strings. There may be perfectly valid reasons why $a != $a, or where $a - $a != 0, or anything else that a poor mathematician would take as completely axiomatic.

    Have you considered asking it?

    <code> use Scalar::Util 'looks_like_number', 'blessed'; require overload; sub is_numeric { local $_=shift; if (!defined $_) { return; } if (!ref $_) { return looks_like_number($_); } if (!blessed $_) { return; } if (!overload::Overloaded $_) { return; } if ($_->can('is_numeric')) { return $_->is_numeric; } # or just return; if it should be false per default return 1; }

    Update: changed ref $_ to !ref $_, thanks, dragonchild, for the /msg.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

I don't understand.
by wolfger (Deacon) on Nov 18, 2004 at 19:54 UTC
    Please forgive my newbie ignorance, but I have read the post twice now, and I still don't understand why
    if($x =~ m/^[0-9]*$/)
    doesn't satisfy your needs.

    --
    Believe nothing, no matter where you read it, or who said it - even if I have said it - unless it agrees with your own reason and your own common sense.
    (Buddha)

      Well, your regexp won't properly identify:

      1.5 -3 -2.89 .32 0.5 -.01

      ...as numbers. It will also fail to reject empty strings.


      Dave

        Ok, here's a bit of enhancement:
        foreach $abc ("asd","","0","123","-12","1.2","-1.2","12-","-a12","a1") {print "$abc:", is_a_num($abc) ? "Number" : "NaN", "\n";} sub is_a_num { return ($_[0] =~ /^(\+|-)?([0-9]|\.)+$/) || 0 ; }
        Thierry

      Among other reasons:

      sub is_a_num { $_[0] =~ m/^[0-9]*$/ } my $not_at_all = ''; print is_a_num( $not_at_all ) ? "Yeppers.\n" : "Nopers.\n";

      Beware the Star of Regex!

      Because if $x is an object, then your regex won't work. (Nevermind that it won't work for reals or negative numbers.)

      A stringification method (which your regex assumes) isn't reliable, because I could write a stringification method to output the number in hexidecimal, Base64, or as a roman numeral (see below) but as far as Perl was concerned, it could still operate just like a number):

      package RomanNumber; use Roman 'roman'; sub new { my $class = shift; my $self = { value => shift }; bless $self, $class; } sub as_num { my $self = shift; return $self->{value}; } sub as_string { my $self = shift; return roman($self->as_num); } sub compare { my ($a,$b) = @_; return ($a->as_num <=> $b->as_num); } use overload '0+' => \&as_num, '""' => \&as_string, '<=>' => \&compare;

      If I add a few more numeric operators (it's an incomplete example), Perl can treat it just like a number, except that it outputs a roman numeral in string context.

      There are cases where I would like to require a value to be number, but I would like to give the user of a module the ability to use a 'number-like' object rather than a number.