How do I set the floating point precision in Perl? - perl

Is there a way to set a Perl script's floating point precision (to 3 digits), without having to change it specifically for every variable?
Something similar to TCL's:
global tcl_precision
set tcl_precision 3

Use Math::BigFloat or bignum:
use Math::BigFloat;
Math::BigFloat->precision(-3);
my $x = Math::BigFloat->new(1.123566);
my $y = Math::BigFloat->new(3.333333);
Or with bignum instead do:
use bignum ( p => -3 );
my $x = 1.123566;
my $y = 3.333333;
Then in both cases:
say $x; # => 1.124
say $y; # => 3.333
say $x + $y; # => 4.457

There is no way to globally change this.
If it is just for display purposes then use sprintf("%.3f", $value);.
For mathematical purposes, use (int(($value * 1000.0) + 0.5) / 1000.0). This would work for positive numbers. You would need to change it to work with negative numbers though.

I wouldn't recommend to use sprintf("%.3f", $value).
Please look at the following example:
(6.02*1.25 = 7.525)
printf("%.2f", 6.02 * 1.25) = 7.52
printf("%.2f", 7.525) = 7.53

Treat the result as a string and use substr. Like this:
$result = substr($result,0,3);
If you want to do rounding, do it as string too. Just get the next character and decide.

Or you could use the following to truncate whatever comes after the third digit after the decimal point:
if ($val =~ m/([-]?[\d]*\.[\d]{3})/) {
$val = $1;
}

Related

How to convert number to one type in perl [duplicate]

How do I fix this code so that 1.1 + 2.2 == 3.3? What is actually happening here that's causing this behavior? I'm vaguely familiar with rounding problems and floating point math, but I thought that applied to division and multiplication only and would be visible in the output.
[me#unixbox1:~/perltests]> cat testmathsimple.pl
#!/usr/bin/perl
use strict;
use warnings;
check_math(1, 2, 3);
check_math(1.1, 2.2, 3.3);
sub check_math {
my $one = shift;
my $two = shift;
my $three = shift;
if ($one + $two == $three) {
print "$one + $two == $three\n";
} else {
print "$one + $two != $three\n";
}
}
[me#unixbox1:~/perltests]> perl testmathsimple.pl
1 + 2 == 3
1.1 + 2.2 != 3.3
Edit:
Most of the answers thus far are along the lines of "it's a floating point problem, duh" and are providing workarounds for it. I already suspect that to be the problem. How do I demonstrate it? How do I get Perl to output the long form of the variables? Storing the $one + $two computation in a temp variable and printing it doesn't demonstrate the problem.
Edit:
Using the sprintf technique demonstrated by aschepler, I'm now able to "see" the problem. Further, using bignum, as recommended by mscha and rafl, fixes the problem of the comparison not being equal. However, the sprintf output still indicates that the numbers aren't "correct". That's leaving a modicum of doubt about this solution.
Is bignum a good way to resolve this? Are there any possible side effects of bignum that we should look out for when integrating this into a larger, existing, program?
See What Every Computer Scientist Should Know About Floating-Point Arithmetic.
None of this is Perl specific: There are an uncountably infinite number of real numbers and, obviously, all of them cannot be represented using only a finite number of bits.
The specific "solution" to use depends on your specific problem. Are you trying to track monetary amounts? If so, use the arbitrary precision numbers (use more memory and more CPU, get more accurate results) provided by bignum. Are you doing numeric analysis? Then, decide on the precision you want to use, and use sprintf (as shown below) and eq to compare.
You can always use:
use strict; use warnings;
check_summation(1, $_) for [1, 2, 3], [1.1, 2.2, 3.3];
sub check_summation {
my $precision = shift;
my ($x, $y, $expected) = #{ $_[0] };
my $result = $x + $y;
for my $n ( $x, $y, $expected, $result) {
$n = sprintf('%.*f', $precision, $n);
}
if ( $expected eq $result ) {
printf "%s + %s = %s\n", $x, $y, $expected;
}
else {
printf "%s + %s != %s\n", $x, $y, $expected;
}
return;
}
Output:
1.0 + 2.0 = 3.0
1.1 + 2.2 = 3.3
"What Every Computer Scientist Should Know About Floating-Point Arithmetic"
Basically, Perl is dealing with floating-point numbers, while you are probably expecting it to use fixed-point. The simplest way to handle this situation is to modify your code so that you are using whole integers everywhere except, perhaps, in a final display routine. For example, if you're dealing with USD currency, store all dollar amounts in pennies. 123 dollars and 45 cents becomes "12345". That way there is no floating point ambiguity during add and subtract operations.
If that's not an option, consider Matt Kane's comment. Find a good epsilon value and use it whenever you need to compare values.
I'd venture to guess that most tasks don't really need floating point, however, and I'd strongly suggest carefully considering whether or not it is the right tool for your task.
A quick way to fix floating points is to use bignum. Simply add a line
use bignum;
to the top of your script.
There are performance implications, obviously, so this may not be a good solution for you.
A more localized solution is to use Math::BigFloat explicitly where you need better accuracy.
From The Floating-Point Guide:
Why don’t my numbers, like 0.1 + 0.2 add up to a nice round 0.3, and
instead I get a weird result like
0.30000000000000004?
Because internally, computers use a
format (binary floating-point) that
cannot accurately represent a number
like 0.1, 0.2 or 0.3 at all.
When the code is compiled or
interpreted, your “0.1” is already
rounded to the nearest number in that
format, which results in a small
rounding error even before the
calculation happens.
What can I do to avoid this problem?
That depends on what kind of
calculations you’re doing.
If you really need your results to add up exactly, especially when you
work with money: use a special decimal
datatype.
If you just don’t want to see all those extra decimal places: simply
format your result rounded to a fixed
number of decimal places when
displaying it.
If you have no decimal datatype available, an alternative is to work
with integers, e.g. do money
calculations entirely in cents. But
this is more work and has some
drawbacks.
Youz could also use a "fuzzy compare" to determine whether two numbers are close enough to assume they'd be the same using exact math.
To see precise values for your floating-point scalars, give a big precision to sprintf:
print sprintf("%.60f", 1.1), $/;
print sprintf("%.60f", 2.2), $/;
print sprintf("%.60f", 3.3), $/;
I get:
1.100000000000000088817841970012523233890533447265625000000000
2.200000000000000177635683940025046467781066894531250000000000
3.299999999999999822364316059974953532218933105468750000000000
Unfortunately C99's %a conversion doesn't seem to work. perlvar mentions an obsolete variable $# which changes the default format for printing a number, but it breaks if I give it a %f, and %g refuses to print "non-significant" digits.
abs($three - ($one + $two)) < $some_Very_small_number
Use sprintf to convert your variable into a formatted string, and then compare the resulting string.
# equal( $x, $y, $d );
# compare the equality of $x and $y with precision of $d digits below the decimal point.
sub equal {
my ($x, $y, $d) = #_;
return sprintf("%.${d}g", $x) eq sprintf("%.${d}g", $y);
}
This kind of problem occurs because there is no perfect fixed-point representation for your fractions (0.1, 0.2, etc). So the value 1.1 and 2.2 are actually stored as something like 1.10000000000000...1 and 2.2000000....1, respectively (I am not sure if it becomes slightly bigger or slightly smaller. In my example I assume they become slightly bigger). When you add them together, it becomes 3.300000000...3, which is larger than 3.3 which is converted to 3.300000...1.
Number::Fraction lets you work with rational numbers (fractions) instead of decimals, something like this (':constants' is imported to automatically convert strings like '11/10' into Number::Fraction objects):
use strict;
use warnings;
use Number::Fraction ':constants';
check_math(1, 2, 3);
check_math('11/10', '22/10', '33/10');
sub check_math {
my $one = shift;
my $two = shift;
my $three = shift;
if ($one + $two == $three) {
print "$one + $two == $three\n";
} else {
print "$one + $two != $three\n";
}
}
which prints:
1 + 2 == 3
11/10 + 11/5 == 33/10

Perl rounding error again

The example to show the problem:
having a number 105;
divide with 1000 (result 0.105)
rouded to 2 decimal places should be: 0.11
Now, several scripts - based on answers to another questions:
This is mostly recommented and mostly upvoted solution is using printf.
use 5.014;
use warnings;
my $i = 105;
printf "%.2f\n", $i/1000; #prints 0.10
but prints a wrong result. In the comment to
https://stackoverflow.com/a/1838885 #Sinan Unur says (6 times upvoted comment):
Use sprintf("%.3f", $value) for mathematical purposes too.
but, it didn't works "sometimes"... like above.
The another recommented solution Math::BigFloat:
use 5.014;
use warnings;
use Math::BigFloat;
my $i = 105;
Math::BigFloat->precision(-2);
my $r = Math::BigFloat->new($i/1000);
say "$r"; #0.10 ;(
Wrong result too. Another recommened one bignum:
use 5.014;
use warnings;
use bignum ( p => -2 );
my $i = 105;
my $r = $i/1000;
say "$r"; #0.10 ;(
wrong again. ;(
Now the working ones:
use 5.014;
use warnings;
use Math::Round;
my $i = 105;
say nearest(0.01, $i/1000); #GREAT prints 0.11 :)
good result 0.11, however a comment here https://stackoverflow.com/a/571740
complains about it.
and finally another recommendation "by my own" function:
use 5.014;
use warnings;
my $i = 105;
my $f = $i/1000;
say myround($f,2); # 0.11
sub myround {
my($float, $prec) = #_;
my $f = $float * (10**$prec);
my $r = int($f + $f/abs($f*2));
return $r/(10**$prec);
}
prints 0.11 too, but can't prove it's correctness.
For the reference I was read:
How do you round a floating point number in Perl?
In Perl, how can I limit the number of places after the decimal point but have no trailing zeroes?
How do I set the floating point precision in Perl?
and many-many others...
and finally this too:
http://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-numbers/
what gives a an really good overall view to the problem.
I understand than it is common problem to all languages, but please, after all
above reading - I still have this question:
What is the error-proof way in perl to round a floating point number to N
decimal places - with mathematically correct way, e.g. what will round results
like 105/1000 correctly to N decimal places without "surprises"...
You're expecting a specific behaviour when the number is exactly 0.105, but floating point errors mean you can't expect a number to be exactly what you think it is.
105/1000 is a periodic number in binary just like 1/3 is periodic in decimal.
105/1000
____________________
= 0.00011010111000010100011 (bin)
~ 0.00011010111000010100011110101110000101000111101011100001 (bin)
= 0.10499999999999999611421941381195210851728916168212890625
0.1049999... is less than 0.105, so it rounds to 0.10.
But even if you had 0.105 exactly, that would still round to 0.10 since sprintf rounds half to even. A better test is 155/1000
155/1000
____________________
= 0.00100111101011100001010 (bin)
~ 0.0010011110101110000101000111101011100001010001111010111 (bin)
= 0.1549999999999999988897769753748434595763683319091796875
0.155 should round to 0.16, but it rounds to 0.15 due to floating point error.
$ perl -E'$_ = 155; say sprintf("%.2f", $_/1000);'
0.15
$ perl -E'$_ = 155; say sprintf("%.0f", $_/10)/100;'
0.16
The second one works because 5/10 isn't periodic, and therein lies the solution. As Sinan Unur said, you can correct the error by using sprintf. But you have to round to an integer if you don't want to lose your work.
$ perl -E'
$_ = 155/1000;
$_ *= 1000; # Move decimal point past significant.
$_ = sprintf("%.0f", $_); # Fix floating-point error.
$_ /= 10; # 5/10 is not periodic
$_ = sprintf("%.0f", $_); # Do our rounding.
$_ /= 100; # Restore decimal point.
say;
'
0.16
That will fix the rounding error, allowing sprintf to properly round half to even.
0.105 => 0.10
0.115 => 0.12
0.125 => 0.12
0.135 => 0.14
0.145 => 0.14
0.155 => 0.16
0.165 => 0.16
If you want to round half up instead, you'll need to using something other than sprintf to do the final rounding. Or you could add s/5\z/6/; before the division by 10.
But that's complicated.
The first sentence of the answer is key. You're expecting a specific behaviour when the number is exactly 0.105, but floating point errors mean you can't expect a number to be exactly what you think it is. The solution is to introduce a tolerance. That's what rounding using sprintf does, but it's a blunt tool.
use strict;
use warnings;
use feature qw( say );
use POSIX qw( ceil floor );
sub round_half_up {
my ($num, $places, $tol) = #_;
my $mul = 1; $mul *= 10 for 1..$places;
my $sign = $num >= 0 ? +1 : -1;
my $scaled = $num * $sign * $mul;
my $frac = $scaled - int($scaled);
if ($sign >= 0) {
if ($frac < 0.5-$tol) {
return floor($scaled) / $mul;
} else {
return ceil($scaled) / $mul;
}
} else {
if ($frac < 0.5+$tol) {
return -floor($scaled) / $mul;
} else {
return -ceil($scaled) / $mul;
}
}
}
say sprintf '%5.2f', round_half_up( 0.10510000, 2, 0.00001); # 0.11
say sprintf '%5.2f', round_half_up( 0.10500001, 2, 0.00001); # 0.11 Within tol
say sprintf '%5.2f', round_half_up( 0.10500000, 2, 0.00001); # 0.11 Within tol
say sprintf '%5.2f', round_half_up( 0.10499999, 2, 0.00001); # 0.11 Within tol
say sprintf '%5.2f', round_half_up( 0.10410000, 2, 0.00001); # 0.10
say sprintf '%5.2f', round_half_up(-0.10410000, 2, 0.00001); # -0.10
say sprintf '%5.2f', round_half_up(-0.10499999, 2, 0.00001); # -0.10 Within tol
say sprintf '%5.2f', round_half_up(-0.10500000, 2, 0.00001); # -0.10 Within tol
say sprintf '%5.2f', round_half_up(-0.10500001, 2, 0.00001); # -0.10 Within tol
say sprintf '%5.2f', round_half_up(-0.10510000, 2, 0.00001); # -0.11
There's probably existing solutions that work along the same lines.
In the old Integer math days of programming, we use to pretend to use decimal places:
N = 345
DISPLAY N # Displays 345
DISPLAY (1.2) N # Displays 3.45
We learned a valuable trick when attempting to round sales taxes correctly:
my $amount = 1.344;
my $amount_rounded = sprintf "%.2f", $amount + .005;
my $amount2 = 1.345;
my $amount_rounded2 = sprintf "%.2f", $amount2 + .005;
say "$amount_rounted $amount_rounded2"; # prints 1.34 and 1.35
By adding in 1/2 of the precision, I display the rounding correctly. When the number is 1.344, adding .005 made it 1.349, and chopping off the last digit displays dip lays 1.344. When I do the same thing with 1.345, adding in .005 makes it 1.350 and removing the last digit displays it as 1.35.
You could do this with a subroutine that will return the rounded amount.
Interesting...
There is a PerlFAQ on this subject. It recommends simply using printf to get the correct results:
use strict;
use warnings;
use feature qw(say);
my $number = .105;
say "$number";
printf "%.2f\n", $number; # Prints .10 which is incorrect
printf "%.2f\n", 3.1459; # Prins 3.15 which is correct
For Pi, this works, but not for .105. However:
use strict;
use warnings;
use feature qw(say);
my $number = .1051;
say "$number";
printf "%.2f\n", $number; # Prints .11 which is correct
printf "%.2f\n", 3.1459; # Prints 3.15 which is correct
This looks like an issue with the way Perl stores .105 internally. Probably something like .10499999999 which would be correctly rounded downwards. I also noticed that Perl warns me about using round and rounding as possible future reserved words.
Your custom function should mostly work as expected. Here's how it works and how you can verify it's correct:
sub myround {
my($float, $prec) = #_;
# Prevent div-by-zero later on
if ($float == 0) { return 0; }
# Moves the decimal $prec places to the right
# Example: $float = 1.234, $prec = 2
# $f = $float * 10^2;
# $f = $float * 100;
# $f = 123.4;
my $f = $float * (10**$prec);
# Round 0.5 away from zero using $f/abs($f*2)
# if $f is positive, "$f/abs($f*2)" becomes 0.5
# if $f is negative, "$f/abs($f*2)" becomes -0.5
# if $f is zero, we have a problem (hence the earlier if statement)
# In our example:
# $f = 123.4 + (123.4 / (123.4 * 2));
# $f = 123.4 + (0.5);
# $f = 123.9;
# Then we truncate to integer:
# $r = int(123.9);
# $f = 123;
my $r = int($f + $f/abs($f*2));
# Lastly, we shift the deciaml back to where it should be:
# $r / 10^2
# $r / 100
# 123 / 100
# return 1.23;
return $r/(10**$prec);
}
However, the following it will throw an error for $float = 0, so there's an additional if statement at the beginning.
The nice thing about the above function is that it's possible to round to negative decimal places, allowing you round to the left of the decimal. For example, myround(123, -2) will give 100.
I'd add use bignum to your original code example.
use 5.014;
use warnings;
use bignum;
my $i = 105; # Parsed as Math::BigInt
my $r = $i / 1000; # Overloaded division produces Math::BigFloat
say $r->ffround(-2, +inf); # Avoid using printf and the resulting downgrade to common float.
This solves the error you made in your use Math::BigFloat example by parsing your numbers into objects imediately and not waiting for you to pass the results of a round off error into Math::BigFloat->new
According to my experience, Perl printf/sprintf uses wrong algorithm. I made this conclusion considering at least the following simple example:
# The same floating part for both numbers (*.8830 or *.8829) is expected in the rounded value, but it is different for some reason:
printf("%.4f\n", "7.88295"); # gives 7.8830
printf("%.4f\n", "8.88295"); # gives 8.8829
The integer part should not have any influence in this example, but it has.
I got this result with Perl 5.8.8.

Converting strings to floats

could soemone help me with the following condition, please?
I'm trying to compare $price and $lsec.
if( (sprintf("%.2f", ($price*100+0.5)/100)*1 != $lsec*1) )
{
print Dumper($price,$lsec)
}
Sometimes the dumper prints same numbers(as strings) and jumps in.
Thought, that multiplying with 1 makes floats from them...
Here dumper output:
$VAR1 = '8.5';
$VAR2 = '8.5';
What am I doing wrong?
Thank you,
Greetings and happy easter.
There is a difference between what is stored in a Perl variable and how it is used. You are correct that multiplying by 1 forces a variable to be used as a number. It also causes the number to be stored in the SV data structure that represents the variable to the interpreter. You can use the Devel::Peek module to see what Perl has stored in each variable:
use Devel::Peek;
my $num = "8.5";
Dump $num;
outputs:
SV = PV(0xa0a46d8) at 0xa0c3f08
REFCNT = 1
FLAGS = (POK,pPOK)
PV = 0xa0be8c8 "8.5"\0
CUR = 3
LEN = 4
continuing...
my $newnum = $num * 1;
Dump $num;
Dump $newnum;
outputs:
SV = PVNV(0xa0a46d8) at 0xa0c3f08
REFCNT = 1
FLAGS = (PADMY,NOK,POK,pIOK,pNOK,pPOK)
IV = 8
NV = 8.5
PV = 0xa0be8c8 "8.5"\0
CUR = 3
LEN = 4
SV = NV(0x9523660) at 0x950df20
REFCNT = 1
FLAGS = (PADMY,NOK,pNOK)
NV = 8.5
The attributes we are concerned with are PV (string pointer), NV (floating-point number), and IV (integer). Initially, $num only has the string value, but using it as a number (e.g. in multiplication) causes it to store the numeric values. However, $num still "remembers" that it is a string, which is why Data::Dumper treats it like one.
For most purposes, there is no need to explicitly force the use of a string as a number, since operators and functions can use them in the most appropriate form. The == and != operators, for example, coerce their operands into numeric form to do numeric comparison. Using eq or ne instead forces a string comparison. This is one more reason to always use warnings in your Perl scripts, since trying to compare a non-numeric string with == will garner this warning:
Argument "asdf" isn't numeric in numeric eq (==) at -e line 1.
You are correct to say that multiplying a string by 1 will force it to be evaluated as a number, but the numeric != comparator will do the same thing. This is presumably a technique you have acquired from other languages as Perl will generally do the right thing and there is no need to force a cast of either operand.
Lets take a look at the values you're comparing:
use strict;
use warnings;
use Data::Dumper;
my $price = '8.5';
my $lsec = '8.5';
my $rounded_price = sprintf("%.2f", ($price * 100 + 0.5) / 100);
print "$rounded_price <=> $lsec\n";
if ( $rounded_price != $lsec ) {
print Dumper($price,$lsec);
}
output
8.51 <=> 8.5
$VAR1 = '8.5';
$VAR2 = '8.5';
So Perl is correctly saying that 8.51 is unequal to 8.5.
I suspect that your
($price * 100 + 0.5) / 100
is intended to round $price to two decimal places, but all it does in fact is to increase $price by 0.005. I think you meant to write
int($price * 100 + 0.5) / 100
but you also put the value through sprintf which is another way to do the same thing.
Either
$price = int($price * 100 + 0.5) / 100
or
$price = sprintf ".2f", $price
but both is overkill!
This part:
($price*100+0.5)/100)
If you put in 8.5, you get back 8.505. Which naturally is not equal to 8.5. Since you do not change $price, you do not notice any difference.
Perl handles conversion automatically, so you do not need to worry about that.
my $x = "8.5";
my $y = 8.5;
print "Equal" if $x == $y; # Succeeds
The nature of the comparison, == or in your case != converts the arguments to numeric, whether they are numeric or not.
You're doing nothing wrong. Perl converts it to a string before dumping it. For comparisons, use == and != for numeric comparisons and eq and ne for a string comparisons. Perl converts to strings and numbers as needed.
Example:
$ perl -MData::Dumper -e "my $a=3.1415; print Dumper($a);"
$VAR1 = '3.1415';

Unexpected result using POSIX ceil() in Perl

I can't for the life of me figure out why the following produces the result it does.
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil($n);
print "$c ($n)\n";
Sigil-tastic, I know — sorry.
I've solved this for my app as follows:
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil("$n");
print "$c ($n)\n";
...but I'm bewildered as to why that's necessary here.
What's happening is this: $n contains a floating point value, and thus is not exactly equal to 3, on my computer it's 3.00000000000000044409. Perl is smart enough to round that to 3 when printing it, but when you explicitly use a floating point function, it will do exactly what it advertises: ceil that to the next integral number: 4.
This is a reality of working with floating point numbers, and by no means Perl specific. You shouldn't rely on their exact value.
use strict;
use warnings;
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g; # Should be exactly 3.
# But it's not.
print "Equals 3\n" if $n == 3;
# Check it more closely.
printf "%.18f\n", $n;
# So ceil() is doing the right thing after all.
my $c = ceil($n);
print "g=$g t=$t r=$r n=$n c=$c\n";
Obligatory Goldberg reference: What Every Computer Scientist Should Know About Floating-Point Arithmetic.
Using Perl, the ability to treat a string as a number in a numerical operation turns into an advantage because you can easily use sprintf to explicitly specify the amount of precision you want:
use strict; use warnings;
use POSIX qw( ceil );
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil( sprintf '%.6f', $n );
print "$c ($n)\n";
Output:
C:\Temp> g
3 (3)
The problem comes about because, given the finite number of bits available to represent a number, there are only a large but finite number of numbers that can be represented in floating point. Given that there are uncountably many numbers on the real line, this will result in approximation and rounding errors in the presence of intermediate operations.
Some numbers (like 6.65) have an exact representation in decimal, but cannot be represented exactly in the binary floating point that computers use (just like 1/3 has no exact decimal representation). As a result, floating point numbers are frequently slightly different than what you would expect. The result of your calculation is not 3, but about 3.000000000000000444.
The traditional way of handling this is to define some small number (called epsilon), and then consider two numbers equal if they differ by less than epsilon.
Your solution of ceil("$n") works because Perl rounds a floating point number to around 14 decimal places when converting it to a string (thus converting 3.000000000000000444 back to 3). But a faster solution would be to subtract epsilon (since ceil will round up) before computing ceil:
my $epsilon = 5e-15; # Or whatever small number you feel is appropriate
my $c = ceil($n - $epsilon);
A floating point subtraction should be faster than converting to a string and back (which involves a lot of division).
Not a Perl problem, as such
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
main()
{
double n = (6.65 * 4.0 - 6.65) / 6.65;
double c = ceil(n);
printf("c is %g, n was %.18f\n", c, n);
}
c is 4, n was 3.000000000000000444
The other answers have explained why the problem is there, there's two ways to make it go away.
If you can, you can compile Perl to use higher precision types. Configuring with -Duse64bitint -Duselongdouble will make Perl use 64 bit integers and long doubles. These have high enough precision to make most floating point problems go away.
Another alternative is to use bignum which will turn on transparent arbitrary precision number support. This is slower, but precise, and can be used lexically.
{
use bignum;
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil($n);
print "$c ($n)\n";
}
You can also declare individual arbitrary precision numbers using Math::BigFloat

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.