I have the following piece of code in my program:
$val = chr(someFunction());
if($val == " ")
{
#do something
}
elsif($val == 0)
{
#do something else
}
But whenever 0 is passed to $val, the if part executes instead of the elsif which I expect to get executed.
How can I fix this?
Thank You.
The == operator is used to compare numeric values. If you want to compare strings, you should use the eq operator.
if ($val eq " ") ...
There are several ways to fix this (TIMTOWDI). You could import the looks_like_a_number function from the standard Scalar::Util package:
if (looks_like_a_number($val) and $val == 0) {
#do something
}
You could use the string equality operator
if ($val eq 0) {
#do something
}
If you have Perl 5.10, you could use the smart match operator
if ($val ~~ 0) {
#do something
}
And many more. Which method you use depends heavily on what you are trying to achieve.
If you had warnings enabled, you would have known what the problem was.
Run this:
use strict;
use warnings;
my $val = chr(someFunction());
if($val == " ")
{
#do something
}
elsif($val == 0)
{
#do something else
}
sub someFunction {
return 1;
}
And you get:
C:>test.pl
Argument " " isn't numeric in numeric eq (==) at C:\test.pl line 6.
Argument "^A" isn't numeric in numeric eq (==) at C:\test.pl line 6.
Adding use diagnostics gives us this additional explanation:
(W numeric) The indicated string was fed as an argument to an operator
that expected a numeric value instead. If you're fortunate the message
will identify which operator was so unfortunate.
So, since we don't want numeric eq, we want string eq: eq. If you didn't know that already, you could look in perldoc perlop to read about Equality Operators.
This is a classic example of how using the warnings and strict pragmas saves time.
Related
I'm hoping to use Perl's smart-matching to do look-ups against an array that contains both strings and compiled regexes:
do_something($file) unless ($file ~~ [ #global_excludes, $local_excludes ]);
(Both the #global_excludes array and the $local_excludes array reference can contain a mixture of strings or compiled regexes.)
Is smart-matching in Perl that smart? Currently, when I run the above with v5.10.1 I get:
Argument "script.sh" isn't numeric in smart match at test.pl line 422.
Argument "Debug.log" isn't numeric in smart match at test.pl line 422.
Argument "lib.pm" isn't numeric in smart match at test.pl line 422.
...
Why does smartmatch think that $file is a number?
For now, I'm just doing it manually:
do_something($file) unless exclude ($file, [ #global_excludes, $local_excludes ]);
where exclude looks like this:
sub exclude
{
my ($file, $list) = #_;
foreach my $lookup (#$list)
{
if (is_regexp($lookup))
{
return 1 if $file =~ $lookup;
}
else
{
return 1 if $file eq $lookup;
}
}
return 0;
}
Basically, I'm looking to make the solution more Perly.
Yes, this does work. The problem is that one of your excludes is a number, not a string. When the right-hand side of a smartmatch is a number, Perl does an == numeric comparison.
my $s = 'foo';
$s ~~ 2; # means $s == 2, warns "$s isn't numeric"
$s ~~ '2'; # means $s eq '2', no warning
If you intend to do a string comparison, make sure your excludes are strings. If necessary, stringify them first (e.g. #array = map { ref($_) ? $_ : "$_" } #array).
Bug found! Was a simple empty string in one of the elements of
[ #global_excludes, $local_excludes ]
I guess in such case perl 5.10.1 figures an empty string for a number
In Perl class today, a student turned in an assignment which vexes me. We are studying ARGV, but the result was not what I expected. His program (meme.pl) was:
#!/usr/bin/perl
$A = $ARGV[0];
chomp($A);
if ($A == "godzilla"){
print "$A\n";
}
else {
print "We need a monster's name\n";
}
If I type:
% ./meme.pl bob
the result is
% bob
So the variable assignment works, and but the condition ($A == "godzilla") is true no matter what is typed on the command line. I expected that since $ARGV[0] is "bob" and $A=$ARGV[0], then it should not true that $A="godzilla."
What am I missing? I have combed through this code for hours, and I know I am just overlooking some small thing.
Use eq, not ==, to test string equality:
if ($A eq "godzilla"){
More information is available at perldoc perlop.
Note: Adding use strict; and use warnings; to the top of your script would have led you in the right direction.
use strict; and use warnings; should be on...instant F in my book.
But no...evaluations of strings using "==" evaluate all strings - except those that start with a number like '123bob' (see comment below) - as numerical 0. That is why it is evaluating to true - it's "turning into" the statement 0 == 0. use warnings; would have told you something was up.
As many have said - use eq for strings.
More evidence and options can be found here: (http://perlmeme.org/howtos/syntax/comparing_values.html)
The pertinent excerpt (example program):
#!/usr/bin/perl
use strict;
use warnings;
my $string1 = 'three';
my $string2 = 'five';
if ($string1 == $string2) {
print "Equal\n";
} else {
print "Not equal\n";
}
From the above example, you would get warning messages and both strings would evaluate to zero:
Argument "five" isn't numeric in numeric eq (==) at ./test.pl line 8.
Argument "three" isn't numeric in numeric eq (==) at ./test.pl line 8.
Equal
You aren't getting those warnings...just the "Equal", thanks to the absence of use warnings; at the top of your - errr...your student's...cough... - code. ;)
When you are comparing strings, you must use "eq" instead of "==". So replace
($A == "godzilla")
by
($A eq "godzilla")
What the others said is correct about using eq to compare strings. However, the test passes, because when compared numerically with == the string 'bob' and the string 'godzilla' both evaluate to 0, so the test passes and you get bob.
#!/usr/bin/env perl
use warnings;
use 5.12.2;
my $c = 'f'; # could be a number too
if ( $c eq 'd' || $c == 9 ) {
say "Hello, world!";
}
What is the best way, to avoid the 'Argument "f" isn't numeric in numeric eq (==) at ./perl.pl line 7.'-warning?
I suppose in this case I could use "eq" two times, but that doesn't look good.
use Scalar::Util 'looks_like_number';
if ( $c eq 'd' || ( looks_like_number($c) && $c == 9 ) ) {
say "Hello, world!";
}
You could also disable this category of warnings temporarily:
{
no warnings 'numeric';
# your code
}
Not sure why you want to avoid the warning. The warning is telling you that there's a potential problem in your program.
If you're going to compare a number with a string that contains unknown data, then you're either going to have to use 'eq' for the comparison or clean up the data in some way so that you know it looks like a number.
The obvious way to avoid a warning about comparing a non-numeric to a numeric is not to do it! Warnings are there for your benefit - they should not be ignored, or worked around.
To answer what is the best way you need to provide more context - i.e. what does $c represent, and why is it necessary to compare it do 'd' or 9 (and why not use $c eq '9')?
Using a regular expression to see if that is a number:
if(($num=~/\d/) && ($num >= 0) && ($num < 10))
{
# to do thing number
}
Why does Perl have 'elsif' and not 'elseif'?
I am doing something like this:
$somevariable = 0;
if ($idcount==5)
{
# Do something
if (somestatement is true) # 1
{
$somevariable = 1;
}
}
elsif ($idcount > 1 && somevariable = 0)
{
# Do something else here
}
The code never comes to the elsif statement. In this case idcount is actually 5, so it comes to the first if statement. But an inner if statement (1) is not true, so $somevariable is still 0. Since idcount is 5, it should come to elsif as well since $idcount is greater than 1 and $somevariable is still 0.
Maybe I am just tired and not seeing something obvious.
No, anything in an elsif only has a chance to be executed if none of the previous if/elsif conditions have been true, that's the "else" part of it.
If you want both to be evaluated, just make it an if instead of an elsif.
elsif ($idcount > 1 && somevariable = 0)
Should be:
elsif ($idcount > 1 && $somevariable == 0)
The assignment operator returns the assigned value, 0, which equates to false, thus the logical and '&&' always fails.
Here is how the Llama book answers the elsif vs elseif question:
"Because Larry says so."
In any if-elsif-else construct, you only ever execute the first branch whose condition is true. Once that branch executes, the program picks up after the entire if-elsif-else.
If you want to do something with possible multiple branches, you can construct your own case-like syntax with multiple if blocks. There is a given-when construct from Perl 5.10 but its has retrograded into experimental status and may be removed from the language.
As for the spelling of elsif, there are a couple of factors involved.
In C, you write
if( ... ) {
... }
else if ( ... ) {
That sometimes causes a "dangling else" problem. If you had started but didn't finish inserting an if-else if right before another, but distinct, if, you've combined the two through the dangling else. Larry Wall didn't like that, so he combined the 'else if" into one word, "elseif". However, that looked odd to Larry since the rule is "i before e except after c, ...", so he fixed that by taking out the "e" to make it just "elsif".
I don't see what your confusion has to do with the fact that perl uses "elsif" rather than "else if" or "elseif". In every language I've ever used, from pre-1970s-style FORTRAN to Java and including along the way such "highlights" as Easytreive, if you do "if cond ... else ..." the stuff after the else doesn't get executed if the original "cond" is true. That's what "else" means.
In your case, the "cond" is "($idcount==5)", which you already stated is true, and the else part that doesn't get executed because it's true is "if ($idcount > 1 && $somevariable == 0) ...".
Perl has elsif because perl doesn't allow C style "else if" without brackets. Instead, you would need to have something like:
if (x) {
}
else {
if(y) {
}
}
"elsif" in Perl works exactly the same as "elseif", "else if", and "elif" in other languages. It's just a matter of spelling.
Here is the equivalent of what was posted in the question.
use strict;
use warnings;
$somevariable = 0;
if ($idcount==5)
{
# do something
if ( 1 )
{
$somevariable = 1;
}
}
else
{
if ($idcount > 1 && somevariable == 0) # this line was wrong in the question
{
# do something else here
}
}
or
use Modern::Perl;
$somevariable = 0;
given($idcount){
when( 5 ){
# do something
if ( 1 ){
$somevariable = 1;
}
}
when( $_ > 1 && $somevariable == 0 ){
# do something else here
}
}
use Modern::Perl;
is the same as
use strict;
use warnings;
use 5.010;
If you're going to use if...elsif in any language, your conditions should be mutually exclusive. The two conditions in your example could both be true simultaneously, so you should break that up into two separate if statements.
Is there a simple way in Perl that will allow me to determine if a given variable is numeric? Something along the lines of:
if (is_number($x))
{ ... }
would be ideal. A technique that won't throw warnings when the -w switch is being used is certainly preferred.
Use Scalar::Util::looks_like_number() which uses the internal Perl C API's looks_like_number() function, which is probably the most efficient way to do this.
Note that the strings "inf" and "infinity" are treated as numbers.
Example:
#!/usr/bin/perl
use warnings;
use strict;
use Scalar::Util qw(looks_like_number);
my #exprs = qw(1 5.25 0.001 1.3e8 foo bar 1dd inf infinity);
foreach my $expr (#exprs) {
print "$expr is", looks_like_number($expr) ? '' : ' not', " a number\n";
}
Gives this output:
1 is a number
5.25 is a number
0.001 is a number
1.3e8 is a number
foo is not a number
bar is not a number
1dd is not a number
inf is a number
infinity is a number
See also:
perldoc Scalar::Util
perldoc perlapi for looks_like_number
The original question was how to tell if a variable was numeric, not if it "has a numeric value".
There are a few operators that have separate modes of operation for numeric and string operands, where "numeric" means anything that was originally a number or was ever used in a numeric context (e.g. in $x = "123"; 0+$x, before the addition, $x is a string, afterwards it is considered numeric).
One way to tell is this:
if ( length( do { no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
If the bitwise feature is enabled, that makes & only a numeric operator and adds a separate string &. operator, you must disable it:
if ( length( do { no if $] >= 5.022, "feature", "bitwise"; no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
(bitwise is available in perl 5.022 and above, and enabled by default if you use 5.028; or above.)
Check out the CPAN module Regexp::Common. I think it does exactly what you need and handles all the edge cases (e.g. real numbers, scientific notation, etc). e.g.
use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }
Usually number validation is done with regular expressions. This code will determine if something is numeric as well as check for undefined variables as to not throw warnings:
sub is_integer {
defined $_[0] && $_[0] =~ /^[+-]?\d+$/;
}
sub is_float {
defined $_[0] && $_[0] =~ /^[+-]?\d+(\.\d+)?$/;
}
Here's some reading material you should look at.
A simple (and maybe simplistic) answer to the question is the content of $x numeric is the following:
if ($x eq $x+0) { .... }
It does a textual comparison of the original $x with the $x converted to a numeric value.
Not perfect, but you can use a regex:
sub isnumber
{
shift =~ /^-?\d+\.?\d*$/;
}
A slightly more robust regex can be found in Regexp::Common.
It sounds like you want to know if Perl thinks a variable is numeric. Here's a function that traps that warning:
sub is_number{
my $n = shift;
my $ret = 1;
$SIG{"__WARN__"} = sub {$ret = 0};
eval { my $x = $n + 1 };
return $ret
}
Another option is to turn off the warning locally:
{
no warnings "numeric"; # Ignore "isn't numeric" warning
... # Use a variable that might not be numeric
}
Note that non-numeric variables will be silently converted to 0, which is probably what you wanted anyway.
rexep not perfect... this is:
use Try::Tiny;
sub is_numeric {
my ($x) = #_;
my $numeric = 1;
try {
use warnings FATAL => qw/numeric/;
0 + $x;
}
catch {
$numeric = 0;
};
return $numeric;
}
Try this:
If (($x !~ /\D/) && ($x ne "")) { ... }
I found this interesting though
if ( $value + 0 eq $value) {
# A number
push #args, $value;
} else {
# A string
push #args, "'$value'";
}
Personally I think that the way to go is to rely on Perl's internal context to make the solution bullet-proof. A good regexp could match all the valid numeric values and none of the non-numeric ones (or vice versa), but as there is a way of employing the same logic the interpreter is using it should be safer to rely on that directly.
As I tend to run my scripts with -w, I had to combine the idea of comparing the result of "value plus zero" to the original value with the no warnings based approach of #ysth:
do {
no warnings "numeric";
if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}
You can use Regular Expressions to determine if $foo is a number (or not).
Take a look here:
How do I determine whether a scalar is a number
There is a highly upvoted accepted answer around using a library function, but it includes the caveat that "inf" and "infinity" are accepted as numbers. I see some regex stuff for answers too, but they seem to have issues. I tried my hand at writing some regex that would work better (I'm sorry it's long)...
/^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/
That's really 5 patterns separated by "or"...
Zero: ^0$
It's a kind of special case. It's the only integer that can start with 0.
Integers: ^[+-]?[1-9][0-9]*$
That makes sure the first digit is 1 to 9 and allows 0 to 9 for any of the following digits.
Scientific Numbers: ^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$
Uses the same idea that the base number can't start with zero since in proper scientific notation you start with the highest significant bit (meaning the first number won't be zero). However, my pattern allows for multiple digits left of the decimal point. That's incorrect, but I've already spent too much time on this... you could replace the [1-9][0-9]* with just [0-9] to force a single digit before the decimal point and allow for zeroes.
Short Float Numbers: ^[+-]?[0-9]?\.[0-9]+$
This is like a zero integer. It's special in that it can start with 0 if there is only one digit left of the decimal point. It does overlap the next pattern though...
Long Float Numbers: ^[+-]?[1-9][0-9]*\.[0-9]+$
This handles most float numbers and allows more than one digit left of the decimal point while still enforcing that the higher number of digits can't start with 0.
The simple function...
sub is_number {
my $testVal = shift;
return $testVal =~ /^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/;
}
if ( defined $x && $x !~ m/\D/ ) {}
or
$x = 0 if ! $x;
if ( $x !~ m/\D/) {}
This is a slight variation on Veekay's answer but let me explain my reasoning for the change.
Performing a regex on an undefined value will cause error spew and will cause the code to exit in many if not most environments. Testing if the value is defined or setting a default case like i did in the alternative example before running the expression will, at a minimum, save your error log.