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!
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 | [reply] [d/l] [select] |
Re: Hash value test of zero
by tybalt89 (Monsignor) on Jul 26, 2023 at 03:22 UTC
|
use warnings;
and try again...
| [reply] [d/l] |
Re: Hash value test of zero
by eyepopslikeamosquito (Archbishop) on Jul 26, 2023 at 03:53 UTC
|
> 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
No. As mentioned emphatically in the title of On Commenting Out 'use strict;' that is not a good idea. :)
It's also not recommended at How do I post a question effectively? -- which endorses use strict and use warnings,
with no mention of omitting them to "keep the samples simple".
See Also
Updated: Added See Also section.
| [reply] [d/l] [select] |
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. | [reply] [d/l] [select] |
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. | [reply] [d/l] [select] |
|
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:
| [reply] [d/l] [select] |
|
| [reply] [d/l] [select] |
|
|
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. | [reply] [d/l] [select] |
Re: Hash value test of zero
by hippo (Archbishop) 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.
| [reply] [d/l] [select] |
|
|