Re: Testing For Numberness Vs Stringness
by bgreenlee (Friar) on Nov 27, 2004 at 16:59 UTC
|
Under which scenarios does the is_numeric subroutine above not work? I ran a little test and it seems to work fine:
foreach (3,'3',3.0,'3.0',3.1,'3.1') {
print "$_ is ", is_numeric($_) ? '':'not ', "numeric\n";
}
sub is_numeric {
($_[0] & ~ $_[0]) eq "0";
}
$ ./is_numeric.pl
3 is numeric
3 is not numeric
3 is numeric
3.0 is not numeric
3.1 is numeric
3.1 is not numeric
| [reply] [d/l] [select] |
|
|
AHA!
bgreenlee, you have helped me resolve my mystery.
Here's what happened. I originally tested the results like this:
my $a = 3.0;
my $b = '3.0';
my $stringCompareResult = $a eq $b?'true':'false';
my $numberCompareResult = $a == $b?'true':'false';
sub is_numeric {
($_[0] & ~ $_[0]) eq "0";
}
print ("\$stringCompareResult: $stringCompareResult\n");
print ("\$numberCompareResult: $numberCompareResult\n");
my $isNumericResultA = is_numeric($a)?'true':'false';
my $isNumericResultB = is_numeric($b)?'true':'false';
print ("\$isNumericResultA: $isNumericResultA\n");
print ("\$isNumericResultB: $isNumericResultB\n");
When I ran this code, I got the following output:
$stringCompareResult: false
$numberCompareResult: true
$isNumericResultA: true
$isNumericResultB: true
I see, now, thanks to your reply, that when I used $a and $b in the earlier comparison, the interpreter CHANGED their "type". I had forgetting that Perl does this.
When I comment out the comparisons, and run just is_numeric, I get the same results as you.
Thanks, bgreenlee!
| [reply] [d/l] [select] |
Re: Testing For Numberness Vs Stringness
by davido (Cardinal) on Nov 27, 2004 at 16:42 UTC
|
What you are seeing is the fact that Perl will happily use a stringified number as a numerified string if it makes sense to do so. That is, to Perl, '3.0' (the string) is the same numerically as 3.0 (the number). It is also true that Perl will happily stringify a number where it makes sense to do so. Where this begins to break down is when you compare the number 3.0 with the string '3.0' using a string comparison. This fails because 3.0 (the number) is actually just 3 (the number). Stringified, that is '3' (the string). When you compare '3' eq '3.0', you are going to get inequality, since as strings go, they're not the same.
| [reply] [d/l] |
|
|
| [reply] |
|
|
Have sepperate functions for numeric vs stringy tests, then. The answer to "is 03 the same as 3" depends on intentions, and not any occult flags of the 3.0 and the 3 that your program could concivably read.
| [reply] |
|
|
Re: Testing For Numberness Vs Stringness
by Juerd (Abbot) on Nov 27, 2004 at 19:04 UTC
|
You use looks_like_number from Scalar::Util. That is the best you can get it. Please note that you can't create a real is_number because there's no good answer to that. Something can look like a number but be a string, or be a string and a number simultaneously (see dualvar in aforementioned module and the special variable $!.
It is possible to see if a variable has a number, though. See if the IOK or NOK flags is set. I'm not telling how this can be done, because I think it's wrong to do this. It only leads to implicit trouble. Look for Devel modules. It's better to let the caller decide how something should be interpreted. In case of DBI placeholders, I'd like different placeholders for different types. I have always wondered why it wasn't more sprintf-like.
| [reply] |
Re: Testing For Numberness Vs Stringness
by bart (Canon) on Nov 28, 2004 at 01:23 UTC
|
I've tried Scalar::Util::looks_like_number and also the following:
sub is_numeric {
($_[0] & ~ $_[0]) eq "0";
}
Perhaps a bit of explanation on how this works, might be welcomed. Bitwise operators are the only operators in Perl that actually behave differently on two numbers (bitwise manipulation of integers) than on two strings (bitwise manipulation of each byte of the strings — on two operands, it's combining byte $i of string A with byte $i of string B). n.b. If one of the two arguments is a number, the other one will be converted to a number, too, possibly with a warning as a consequence. It may also change the nature of that scalar, as it'll be thought of by perl as a number, afterwards.
So, the two basic cases are:
- A numerical argument. This argument is treated as an integer, and is bitwise inverted as an integer, thus every bit is flipped in the result. Bitwise and on these two integers, $n & ~$n, results in an integer with every bit cleared, as there are no bits set in both arguments (They're opposites, remember?) This one integer, with all bits cleared, is zero. Converted to a string, this will be "0".
- A string as argument. This string is first inverted as a string, byte by byte. $s & ~$s will also be a string, containing only null bytes for the same reason as above; thus the result will be "\0" x length $s. Note the difference between "0", chr(48) in Ascii, and "\0", chr(0). The result's length may vary, but the result string will never be "0".
And that is how it works.
| [reply] [d/l] [select] |
|
|
Thanks, Bart. Perl's habit of permanently converting a var into a number is, in my opinion, at least an aggravating hack even if not an actual bug. In any case, your warning is well-taken. The comparion routine should work on a copy of its arg.
| [reply] |
Re: Testing For Numberness Vs Stringness
by ikegami (Patriarch) on Nov 27, 2004 at 18:19 UTC
|
If you really need to differentiate between the two, you might need to store some metadata. A simplistic example:
sub are_equal {
return unless ($_[0][0] eq $_[1][0]);
return $_[0][1] eq $_[1][1] if ($_[0][0] eq 'STRING');
return $_[0][1] == $_[1][1] if ($_[0][0] eq 'NUMBER');
die("Bad data");
}
$a = [ NUMBER => 3 ];
$b = [ STRING => '3.0' ];
print(are_equal($a, $b)?'equal':'not equal', $/);
A more comprehensive, more scalable solution might use classes with overrided operators.
| [reply] [d/l] |
Re: Testing For Numberness Vs Stringness
by rrwo (Friar) on Dec 02, 2004 at 01:06 UTC
|
That's funny, weren't we just talking about something similar here?
Scalar::Util looks to see if the internal number portion of the scalar is set, not if the contents of a string are actually a number.
| [reply] |