Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Hash value test of zero

by themcp (Novice)
on Jul 26, 2023 at 01:53 UTC ( [id://11153571]=perlquestion: print w/replies, xml ) Need Help??

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

Hello all,

This one has been giving me some grief for awhile now, and it usually isn't an issue, but every now and then it comes up and could use some help in grasping what is going on and a possible change to my approach. (have looked online but can't find a definitive answer)

This occurs whether strict is called or not, and it also happens whether "my" is declared on the hash name or not, so keeping the samples simple and omitting that part...


Here's the test code:


$hashname{foo}='a string';

if($hashname{foo} == 0){
  print "It is zero\n";
}else{
  print "It is NOT zero\n";
}


Run it, and you get:
It is zero

In reading online, it appears that hash values may be treated as strings by default, rather than pure numeric. As a huge amount of my code uses hashes to do both "eq" type tests in addition to numeric "==" tests on a hash value that is stored as a number, it'd help to get some insight on why this isn't working so that I don't introduce bugs in the code. In the sample above, I need it to be sensing the fact that the value stored IS, in fact, a numeric zero. This test result almost appears as though the test itself is setting it to zero, but this can't be the case. It isn't undef, because "a string" has been stored in the hash value memory space. It can't be a numeric zero, because it hasn't been set as such, and it can't be a null value for the same reason that there is a string stored there. Just not sure what is running under the hood that is producing this result. This issue ONLY happens when testing for a numeric 0.


So... one additional test on 0 but with a null value being set:


$hashname{foo}="";
if($hashname{foo} == 0){
  print "It is zero\n";
}else{
  print "It is NOT zero\n";
}


Run it, and you get:
It is zero


I could potentially see this one returning as true/correct for the test, since it could be looking at it as a null value being tested but I'm not super familiar with how Perl handles this in the background. It still messes up my logic handlers, though, because if I need to test for an actual 0 being stored rather than a null value, I'm out of luck.


Along this same line of thought but getting away from the "0" or string values, here's another test:


$hashname{foo}=3;

if($hashname{foo} == 3){
  print "First test: It is 3\n";
}else{
  print "First test: It is NOT 3\n";
}
if($hashname{foo} == 4){
  print "Second test: It is 4\n";
}else{
  print "Second test: It is NOT 4\n";
}


Run it, and you get:

First test: It is 3
Second test: It is NOT 4

So, in the third/final test, it is correctly seeing it as a numeric "3", because it can differentiate between the two and shows that it is not a "4".


If anyone can enlighten me with a deeper understanding of what Perl is doing here, I'd sure appreciate it. At the moment, I can't rely on the code correctly returning a true numeric test for 0 being stored in the hash value memory space... would be great to avoid future WTF moments when building logic routines that actually need that test type to work!

Thanks in advance!

Replies are listed 'Best First'.
Re: Hash value test of zero
by syphilis (Archbishop) on Jul 26, 2023 at 05:17 UTC
    Firstly, note that this is not limited to hash values.
    You'll get the same thing if you simply do $foo = 'a string'; and then test $foo == 0, etc.

    The == operator compares both sides in numeric context.
    For your first test, the string 'a string' evaluates to 0 in numeric context and therefore the numeric equivalence holds. (Note that the empty string"", assigned in a later test, also evaluates to 0 in numeric context.)

    With the examples you've provided, you'll get the results you're after by simply replacing == with eq throughout, as that will instead compare both sides as strings (ie in string context).

    Cheers,
    Rob
Re: Hash value test of zero
by tybalt89 (Monsignor) on Jul 26, 2023 at 03:22 UTC
Re: Hash value test of zero
by eyepopslikeamosquito (Archbishop) on Jul 26, 2023 at 03:53 UTC
Re: Hash value test of zero
by dsheroh (Monsignor) on Jul 26, 2023 at 07:50 UTC
    As mentioned in a previous reply, == does a numeric comparison. It forces both of the values it is comparing to be treated as numbers.

    When a string value gets evaluated numerically, a very simple method is used to convert it to a number: If there is a (decimal) number at the start of the string, that number is read as the numeric value. If there is no number at the start of the string, the numeric value is 0. So "3.14 is pi" == 3.14, but "pi is 3.14" == 0. This check ignores leading whitespace, so "   3" == 3 rather than 0.

    Note that this behavior is not specific to hashes, nor even specific to Perl. C's atoi() function behaves the same way (which is where I presume Perl inherited it from) and it may go back even farther than that.

Re: Hash value test of zero
by Marshall (Canon) on Jul 26, 2023 at 09:01 UTC
    As others have said, always use warings; use strict;

    When you do a math operation, == or +/- etc, Perl will convert a string value to a numeric value according to its rules. Trying to use a string that does not exactly represent a number as a number causes a warning with one exception shown below.

    Here is some example code:

    use strict; use warnings; $|=1; #turn off stdout buffering so error msgs match my $x = "awsdfasdf"; print "zero\n" if ($x==0); # zero #Argument "awsdfasdf" isn't numeric in numeric eq (==) $x+= 0; print "x\n"; #0 my $y="asdf6"; #ending digits to alpha do not count $y+= 0; #Argument "asdf6" isn't numeric in addition (+) print "$y\n"; #0 my $z = "3asdf"; #beginning digits will be used $z+=0; #Argument "3asdf" isn't numeric in addition (+) print "$z\n"; #3 my $x1 = "0 but true"; #more common now is 0E0 $x1+=0; # NO ERROR! print "$x1\n"; #0 my $x2 = "3 camels"; #can have textual "units" $x2+=0; #Argument "3 camels" isn't numeric in addition (+) print "$x2\n"; #3 __END__ Argument "awsdfasdf" isn't numeric in numeric eq (==) at line 7. zero x Argument "asdf6" isn't numeric in addition (+) at line 14. 0 Argument "3asdf" isn't numeric in addition (+) at line 20. 3 0 Argument "3 camels" isn't numeric in addition (+) at line 31. 3
    As the above code shows the "0 but true" exception is hard-coded into Perl as an exception. This allows a function to return a single value that can represent both logically true and numerically zero. Using that string as a numeric value will not cause a warning. This is now seldom seen as the exponential value 0E0 is more commonly used, especially by the DBI.

      As the above code shows the "0 but true" exception is hard-coded into Perl as an exception. This allows a function to return a single value that can represent both logically true and numerically zero. Using that string as a numeric value will not cause a warning. This is now seldom seen as the exponential value 0E0 is more commonly used, especially by the DBI.

      Interesting. From perlop:

      Perl operators that return true or false generally return values that can be safely used as numbers. For example, the relational operators in this section and the equality operators in the next one return 1 for true and a special version of the defined empty string, "", which counts as a zero but is exempt from warnings about improper numeric conversions, just as "0 but true" is.

      In my experience, this is well-known ... well much better known than the quirky dualvar ... which caused a sensation here recently when uncorked by marioroy during the long running Long List is Long saga. Mario is the only Perl programmer I know who has used dualvar in production code (in MCE::Shared::Cache part of MCE).

      See also:

        That perlop quote is a little out of date. It's not wrong per-se, but the boolean values returned by most operators are now more succinctly described as builtin::true and builtin::false.

        Mario is the only Perl programmer I know who has used dualvar in production code

        Well now you know two, because I delight in using dualvars for XS constants :-)

        use Fluent::LibFluentBit qw( FLB_LIB_NO_CONFIG_MAP ); say FLB_LIB_NO_CONFIG_MAP; say 0+FLB_LIB_NO_CONFIG_MAP;
        Output:
        FLB_LIB_NO_CONFIG_MAP 2

        The great part is that when you pass them to XS functions, they pass the integer that XS is looking for, and when you dump those parameters to STDOUT or a logger, you get the constant name so you can understand what is going on.

Re: Hash value test of zero
by hippo (Bishop) on Jul 26, 2023 at 08:57 UTC

    If you want to roll your own then something like this should do. Feel free to expand @zero and @notzero as desired.

    use strict; use warnings; use Test::More; my @zero = (0, 0.0, -0, -0.0, 0E0); my @notzero = (1, -1, 1.1, -1.1, '', undef, 'foo', 'a string', 3, 4, \ +@zero); plan tests => @zero + @notzero; for my $scalar (@zero) { ok is_zero ($scalar), "$scalar is zero"; } for my $scalar (@notzero) { ok !is_zero ($scalar), ($scalar // 'undef') . ' is not zero'; } sub is_zero { my $scalar = shift; return defined ($scalar) && length ($scalar) && (!$scalar || $scal +ar eq '0E0'); }

    Personally, I would probably go with Scalar::Util::looks_like_number.

    This occurs whether strict is called or not, and it also happens whether "my" is declared on the hash name or not, so keeping the samples simple and omitting that part...

    It is generally a bad idea not to show the entirety of the code producing the problematic result. See SSCCE for more on this.


    🦛

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11153571]
Approved by Ratazong
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (4)
As of 2024-05-27 18:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found