Unexpected result for IF statement: "string" <= 72 is true? - perl

In my code below, when I enter in some non-numeric letters at the input (ie. $temp), it responds with "Too cold!" instead of "invalid". What am I missing?
#!/usr/bin/perl
print "What is the temperature outside? ";
$temp=<>;
if ($temp > 72) {
print "Too hot!\n"; }
elsif ($temp <= 72) {
print "Too cold!\n"; }
else {
print "Temperature $temp is invalid.\n"; }

This is because it will be treated as 0 if it cannot be converted into a number. You should check before if the response has only numbers, or restrict the input in any other way so that only a valid number can be entered. Something along the lines:
print "invalid" if ($temp =~ /\D/);
(prints invalid if $temp contains any non-digit character. Note that this may invalidate "+" and "-", but you get the idea).

The numerical comparison operators expect their arguments to be numbers. If you try to compare a string like 'foo' using a numerical comparison, it will be converted silently to the number 0, which is less than 72.
If you had warnings turned on, you would have been told what was going on.
friedo$ perl -Mwarnings -E 'say "foo" < 72'
Argument "foo" isn't numeric in numeric lt (<) at -e line 1.
1
This is why you should always begin your programs with
use strict;
use warnings;

Casting an invalid numerical string to a number results in 0, therefor you could use something as the below to see if the input was indeed valid or not.
print "What is the temperature outside? ";
$temp=<>;
if ($temp == 0 && $temp ne '0') {
print "Temperature $temp is invalid.\n"; }
elsif ($temp > 72) {
print "Too hot!\n"; }
elsif ($temp <= 72) {
print "Too cold!\n"; }
Explanation: If the input string was casted into 0 (zero) though the string itself isn't equal to '0' (zero) the input is not numeric, hence; invalid.
You could also check to see if the input only consists of [0-9.] by using a regular expression, that would ensure that it's a valid number (also remember that numbers do not start with 0 (zero) and then have digits that follow, unless you are writing in octal.
Note: Remember to trim the input string from white spaces before the above check.

For precisely this reason (and many others), you're MUCH better off if you enable "use warnings":
#!/usr/bin/perl
use strict;
use warnings;
...

Try it after removing the trailing newline, which is probably what's causing Perl to treat it as a string rather than a number:
chomp( my $test = <> );

Related

'ne' is not working in do-while loop, whereas '!=' works

I'm not sure what mistake I'm making, but I just changed ne to != and it worked.
This is a simple program to let the user guess a number until they hit a target number.
#!/usr/bin/perl
my $guess = 1;
do {
$guess = <STDIN>;
} while ( $guess != 12 ) ; # it doesn't work if i replace != with ne
say "you guessed ", $guess;
Perl's ne is the string not-equal operator, so $guess and 12 are treated as strings.
A string obtained via <> contains a newline character at the end, so it is not equal to the string '12'.
!= is the numeric not-equal operator, so both operands are treated as numbers. In this case Perl will ignore any trailing non-numeric characters when making the conversion, so the newline is ignored and the string 12<newline> is treated as numeric 12.
Were you to chomp the obtained value before comparison, the ne operator would also work.

Substituting text string in uninitialized table fields having numeric sprintf format

I need to format a table row having fields of various numeric data. Some fields have integers and others have real numbers that have to be formatted with a fixed precision. Right justified numbers might be left padded with spaces, others with zeros.
Obviously sprintf is the perfect tool for the job.
However, occasionally a table field is undefined, being NULL, n/a and likewise. In such cases I usually have to print a text string like 'NULL', 'undef' or other. (Even leaving it uninitialized makes Perl issue a warning.)
Just plugging in the text string won't do. Beside issuing a warning, a sprintf of say '% 11.2' not only issues a warning "Argument "NULL" isn't numeric in sprintf at..." but also prints zero rather than the text string.
Any idea as to how to do what I need?
You could try using the "%s" format instead and a helper sub routine: For example:
use strict;
use warnings;
my $num = "N/A";
printf "%s\n", format_float( $num );
sub format_float {
return ($_[0] eq "N/A") ? $_[0] : sprintf "%f", $_[0];
}
I suggest that you use printf with a major format that uses just string fields %s together with a set of conditional expressions that return either a number formatted with sprintf if the value is defined, or the string NULL if it is not
An example should make that clearer
my ($f1, $f2, $f3) = ( 700/3, undef, 1/7 );
printf "%11s %11s %11s\n",
defined $f1 ? sprintf('%.2f', $f1) : 'NULL',
defined $f2 ? sprintf('%.2f', $f2) : 'NULL',
defined $f3 ? sprintf('%.2f', $f3) : 'NULL';
output
233.33 NULL 0.14
If you can be 99% certain that all non-number "values" you need to deal with will not have a digit anywhere, this is probably what you want:
sprintf (($num =~ /\d/) ? "%11.2f" : "%s", $num)
If you need to additionally distinguish integral from float numbers, you'll have to do more...

how to check an empty input and regulate the type of input in perl

Guys how do i check an empty input in perl? Including spaces,tabs,newlines etc..
here's my sample code but its not working: what am i doing wrong?
my $number=int (1+rand 100);
while (<>) {
chomp $_;
last if ($_=~/exit|quit/i or $_ eq $number);
print "too high! \n" if (defined $_ && $_ > $number);
print "too low!\n" if (defined $_ && $_ < $number);
print $_;
}
So basically, the user input something, if it's a number it compares to the default random number. It prints low or high depending on the number. But when i just press enter without entering something it still goes to that if statement and gives an error that what i entered isnt numeric (due to this code $_ < $number).
So another question is how to handle input to allow only the word "exit" or "quit" and numbers. Other than that it exits.
The while(<>){...} will loop as long as the return value of the <> is defined, i.e. you aren't at EOF. So $_ is always defined inside the loop.
To assert that some input is numeric, you can use looks_like_number from Scalar::Util or use a simple regex:
unless (/\A[0-9]+\z/) {
print "not numeric!\n";
next;
}
After that, we can treat the value of $_ as a number integer, and can be sure that use warnings won't complain. E.g.
# remove newline from input
chomp;
# Test for abort condition
last if /\b(?:quit|exit)\b/;
# Assert numeric input
unless (/\A[0-9]+\z/) {
print "not numeric!\n";
next;
}
# Check input against secret $number
if ($_ == $number) {
print "Correct!\n";
last;
} elsif ($_ < $number) {
print "too low\n";
} elsif ($_ > $number) {
print "too high\n";
}
Sorry for the silly question, Got the answer to my first question.
you just have to add this code to the first condition
$_=~/^\s*$/
which compares if the input is any whitespace
and for the second one to limit only the "exit" and "quit" as a valid non digit input add this one to the first regex comparison in the first condition
$_=~/(exit|quit|\D)/i
notice the \D which only matches non digit characters. But since its an "OR" it will short circuit once a specific non digit character (exit or quit) is entered, terminating the loop instantly.
Thanks guys

Warnings on equality operators

Has something changed in Perl or has it always been this way, that examples like the second ($number eq 'a') don't throw a warning?
#!/usr/bin/env perl
use warnings;
use 5.12.0;
my $string = 'l';
if ($string == 0) {};
my $number = 1;
if ($number eq 'a') {};
# Argument "l" isn't numeric in numeric eq (==) at ./perl.pl line 6.
Perl will be try to convert a scalar to the type required by the context where it is used.
There is a valid conversion from any scalar type to a string, so this is always done silently.
Conversion to a number is also done silently if the string passes a looks_like_number test (accessible through Scalar::Util). Otherwise a warning is raised and a 'best guess' approximation is done anyway.
my $string = '9';
if ( $string == 9 ) { print "YES" };
Converts the string silently to integer 9, the test succeeds and YES is printed.
my $string = '9,8';
if ( $string == 9 ) { print "YES" };
Raises the warning Argument "9,8" isn't numeric in numeric eq (==), converts the string to integer 9, the test succeeds and YES is printed.
To my knowledge it has always been this way, at least since v5.0.
It has been that way.
In the first if, l is considered to be in numeric context. However, l cannot be converted to a number. Therefore, a warning is emitted.
In the second if, the number 1 is considered to be in string context. Therefore the number 1 is converted to the string '1' before comparison and hence no warnings are emitted.
Did you use a lowercase "L" on purpose? It's often hard to tell the difference between a lowercase "L" and one. You would have answered your own question if you had used a one instead.
>perl -wE"say '1' == 0;"
>perl -wE"say 1 eq 'a';"
>
As you can see,
If one needs a number, Perl will convert a string to a number without warning.
If one needs a string, Perl will convert a number to a string without warning.
Very consistent.
You get a warning when you try to convert a lowercase L to a number, but how is that surprising?

How do I tell if a variable has a numeric value in Perl?

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.