How do you round a floating point number in Perl? - perl

How can I round a decimal number (floating point) to the nearest integer?
e.g.
1.2 = 1
1.7 = 2

Output of perldoc -q round
Does Perl have a round() function? What about ceil() and floor()?
Trig functions?
Remember that int() merely truncates toward 0. For rounding to a certain number of digits, sprintf() or printf() is usually the easiest
route.
printf("%.3f", 3.1415926535); # prints 3.142
The POSIX module (part of the standard Perl distribution) implements
ceil(), floor(), and a number of other mathematical and trigonometric
functions.
use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
In 5.000 to 5.003 perls, trigonometry was done in the Math::Complex
module. With 5.004, the Math::Trig module (part of the standard Perl
distribution) implements the trigonometric functions. Internally it
uses the Math::Complex module and some functions can break out from the
real axis into the complex plane, for example the inverse sine of 2.
Rounding in financial applications can have serious implications, and
the rounding method used should be specified precisely. In these
cases, it probably pays not to trust whichever system rounding is being
used by Perl, but to instead implement the rounding function you need
yourself.
To see why, notice how you'll still have an issue on half-way-point
alternation:
for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Don't blame Perl. It's the same as in C. IEEE says we have to do
this. Perl numbers whose absolute values are integers under 2**31 (on
32 bit machines) will work pretty much like mathematical integers.
Other numbers are not guaranteed.

Whilst not disagreeing with the complex answers about half-way marks and so on, for the more common (and possibly trivial) use-case:
my $rounded = int($float + 0.5);
UPDATE
If it's possible for your $float to be negative, the following variation will produce the correct result:
my $rounded = int($float + $float/abs($float*2 || 1));
With this calculation -1.4 is rounded to -1, and -1.6 to -2, and zero won't explode.

You can either use a module like Math::Round:
use Math::Round;
my $rounded = round( $float );
Or you can do it the crude way:
my $rounded = sprintf "%.0f", $float;

If you decide to use printf or sprintf, note that they use the Round half to even method.
foreach my $i ( 0.5, 1.5, 2.5, 3.5 ) {
printf "$i -> %.0f\n", $i;
}
__END__
0.5 -> 0
1.5 -> 2
2.5 -> 2
3.5 -> 4

See perldoc/perlfaq:
Remember that int() merely truncates toward 0. For rounding to a
certain number of digits, sprintf() or printf() is usually the
easiest route.
printf("%.3f",3.1415926535);
# prints 3.142
The POSIX module (part of the standard Perl distribution)
implements ceil(), floor(), and a number of other mathematical
and trigonometric functions.
use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
In 5.000 to 5.003 perls, trigonometry was done in the Math::Complex module.
With 5.004, the Math::Trig module (part of the standard Perl distribution) > implements the trigonometric functions.
Internally it uses the Math::Complex module and some functions can break
out from the real axis into the complex plane, for example the inverse sine of 2.
Rounding in financial applications can have serious implications, and the rounding
method used should be specified precisely. In these cases, it probably pays not to
trust whichever system rounding is being used by Perl, but to instead implement the
rounding function you need yourself.
To see why, notice how you'll still have an issue on half-way-point alternation:
for ($i = 0; $i < 1.01; $i += 0.05)
{
printf "%.1f ",$i
}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7 0.8 0.8 0.9 0.9 1.0 1.0
Don't blame Perl. It's the same as in C. IEEE says we have to do
this. Perl numbers whose absolute values are integers under 2**31 (on
32 bit machines) will work pretty much like mathematical integers.
Other numbers are not guaranteed.

You don't need any external module.
$x[0] = 1.2;
$x[1] = 1.7;
foreach (#x){
print $_.' = '.( ( ($_-int($_))<0.5) ? int($_) : int($_)+1 );
print "\n";
}
I may be missing your point, but I thought this was much cleaner way to do the same job.
What this does is to walk through every positive number in the element, print the number and rounded integer in the format you mentioned. The code concatenates respective rounded positive integer only based on the decimals. int($_) basically round-down the number so ($-int($)) captures the decimals. If the decimals are (by definition) strictly less than 0.5, round-down the number. If not, round-up by adding 1.

The following will round positive or negative numbers to a given decimal position:
sub round ()
{
my ($x, $pow10) = #_;
my $a = 10 ** $pow10;
return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}

Following is a sample of five different ways to summate values. The first is a naive way to perform the summation (and fails). The second attempts to use sprintf(), but it too fails. The third uses sprintf() successfully while the final two (4th & 5th) use floor($value + 0.5).
use strict;
use warnings;
use POSIX;
my #values = (26.67,62.51,62.51,62.51,68.82,79.39,79.39);
my $total1 = 0.00;
my $total2 = 0;
my $total3 = 0;
my $total4 = 0.00;
my $total5 = 0;
my $value1;
my $value2;
my $value3;
my $value4;
my $value5;
foreach $value1 (#values)
{
$value2 = $value1;
$value3 = $value1;
$value4 = $value1;
$value5 = $value1;
$total1 += $value1;
$total2 += sprintf('%d', $value2 * 100);
$value3 = sprintf('%1.2f', $value3);
$value3 =~ s/\.//;
$total3 += $value3;
$total4 += $value4;
$total5 += floor(($value5 * 100.0) + 0.5);
}
$total1 *= 100;
$total4 = floor(($total4 * 100.0) + 0.5);
print '$total1: '.sprintf('%011d', $total1)."\n";
print '$total2: '.sprintf('%011d', $total2)."\n";
print '$total3: '.sprintf('%011d', $total3)."\n";
print '$total4: '.sprintf('%011d', $total4)."\n";
print '$total5: '.sprintf('%011d', $total5)."\n";
exit(0);
#$total1: 00000044179
#$total2: 00000044179
#$total3: 00000044180
#$total4: 00000044180
#$total5: 00000044180
Note that floor($value + 0.5) can be replaced with int($value + 0.5) to remove the dependency on POSIX.

Negative numbers can add some quirks that people need to be aware of.
printf-style approaches give us correct numbers, but they can result in some odd displays. We have discovered that this method (in my opinion, stupidly) puts in a - sign whether or not it should or shouldn't. For example, -0.01 rounded to one decimal place returns a -0.0, rather than just 0. If you are going to do the printf style approach, and you know you want no decimal, use %d and not %f (when you need decimals, it's when the display gets wonky).
While it's correct and for math no big deal, for display it just looks weird showing something like "-0.0".
For the int method, negative numbers can change what you want as a result (though there are some arguments that can be made they are correct).
The int + 0.5 causes real issues with -negative numbers, unless you want it to work that way, but I imagine most people don't. -0.9 should probably round to -1, not 0. If you know that you want negative to be a ceiling rather than a floor then you can do it in one-liner, otherwise, you might want to use the int method with a minor modification (this obviously only works to get back whole numbers:
my $var = -9.1;
my $tmpRounded = int( abs($var) + 0.5));
my $finalRounded = $var >= 0 ? 0 + $tmpRounded : 0 - $tmpRounded;

If you are only concerned with getting an integer value out of a whole floating point number (i.e. 12347.9999 or 54321.0001), this approach (borrowed and modified from above) will do the trick:
my $rounded = floor($float + 0.1);

My solution for sprintf
if ($value =~ m/\d\..*5$/){
$format =~ /.*(\d)f$/;
if (defined $1){
my $coef = "0." . "0" x $1 . "05";
$value = $value + $coef;
}
}
$value = sprintf( "$format", $value );

loads of reading documentation on how to round numbers, many experts suggest writing your own rounding routines, as the 'canned' version provided with your language may not be precise enough, or contain errors. i imagine, however, they're talking many decimal places not just one, two, or three. with that in mind, here is my solution (although not EXACTLY as requested as my needs are to display dollars - the process is not much different, though).
sub asDollars($) {
my ($cost) = #_;
my $rv = 0;
my $negative = 0;
if ($cost =~ /^-/) {
$negative = 1;
$cost =~ s/^-//;
}
my #cost = split(/\./, $cost);
# let's get the first 3 digits of $cost[1]
my ($digit1, $digit2, $digit3) = split("", $cost[1]);
# now, is $digit3 >= 5?
# if yes, plus one to $digit2.
# is $digit2 > 9 now?
# if yes, $digit2 = 0, $digit1++
# is $digit1 > 9 now??
# if yes, $digit1 = 0, $cost[0]++
if ($digit3 >= 5) {
$digit3 = 0;
$digit2++;
if ($digit2 > 9) {
$digit2 = 0;
$digit1++;
if ($digit1 > 9) {
$digit1 = 0;
$cost[0]++;
}
}
}
$cost[1] = $digit1 . $digit2;
if ($digit1 ne "0" and $cost[1] < 10) { $cost[1] .= "0"; }
# and pretty up the left of decimal
if ($cost[0] > 999) { $cost[0] = commafied($cost[0]); }
$rv = join(".", #cost);
if ($negative) { $rv = "-" . $rv; }
return $rv;
}
sub commafied($) {
#*
# to insert commas before every 3rd number (from the right)
# positive or negative numbers
#*
my ($num) = #_; # the number to insert commas into!
my $negative = 0;
if ($num =~ /^-/) {
$negative = 1;
$num =~ s/^-//;
}
$num =~ s/^(0)*//; # strip LEADING zeros from given number!
$num =~ s/0/-/g; # convert zeros to dashes because ... computers!
if ($num) {
my #digits = reverse split("", $num);
$num = "";
for (my $i = 0; $i < #digits; $i += 3) {
$num .= $digits[$i];
if ($digits[$i+1]) { $num .= $digits[$i+1]; }
if ($digits[$i+2]) { $num .= $digits[$i+2]; }
if ($i < (#digits - 3)) { $num .= ","; }
if ($i >= #digits) { last; }
}
#$num =~ s/,$//;
$num = join("", reverse split("", $num));
$num =~ s/-/0/g;
}
if ($negative) { $num = "-" . $num; }
return $num; # a number with commas added
#usage: my $prettyNum = commafied(1234567890);
}

Using Math::BigFloat you can do something like this:
use Math::BigFloat;
print Math::BigFloat->new(1.2)->bfround(1); ## 1
print Math::BigFloat->new(1.7)->bfround(1); ## 2
This can be wrapped in a subroutine
use Math::BigFloat;
sub round {
Math::BigFloat->new(shift)->bfround(1);
}
print round(1.2); ## 1
print round(1.7); ## 2

cat table |
perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";'

Related

Perl - Unexpected outcome of while loop

This is a simple program but I am unable to understand logic/work being done by while loop behind the scene.
Problem: Write a program that prints every number from 0 to 1 that has a single digit after the decimal place (that is, 0.1, 0.2, and so on).
So here is my code:
$num = 0;
while ( $num < 1 ) {
print "$num \n";
$num = $num + 0.1;
}
If I write it in this way, it is going to print
$num = 0;
while ( $num < 1 ) {
$num = $num + 0.1;
print "$num \n";
}
Output:
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1
1.1
Ideally speaking, 1 and 1.1 should not get printed in both code samples respectively. After printing 0.9 when 0.1 is added to it, it becomes 1.0 i.e. while (1.0 < 1). Hence condition in while loop is false, so it should not print 1 and 1.1. But that is what is happening.
Can someone please explain why while loop is working in this unexpected way i.e. printing 1 and 1.1 even when condition is false.
1/10 is a periodic number in binary just like 1/3 is periodic in decimal.
____
1/10 = 0.00011 base 2
As such, it can't be represented exactly by a floating-point number.
$ perl -e'printf "%$.20e\n", 0.1;'
1.00000000000000005551e-01
This imprecision is the cause of your problem.
$ perl -e'my $i = 0; while ($i < 1) { printf "%1\$.3f %1\$.20e\n", $i; $i += 0.1; }'
0.000 0.00000000000000000000e+00
0.100 1.00000000000000005551e-01
0.200 2.00000000000000011102e-01
0.300 3.00000000000000044409e-01
0.400 4.00000000000000022204e-01
0.500 5.00000000000000000000e-01
0.600 5.99999999999999977796e-01
0.700 6.99999999999999955591e-01
0.800 7.99999999999999933387e-01
0.900 8.99999999999999911182e-01
1.000 9.99999999999999888978e-01
Generally speaking, one can solve this by checking if the number is equal to another within some tolerance. But in this case, there's a simpler solution.
$ perl -e'for my $j (0..9) { my $i = $j/10; printf "%1\$.3f %1\$.20e\n", $i; }'
0.000 0.00000000000000000000e+00
0.100 1.00000000000000005551e-01
0.200 2.00000000000000011102e-01
0.300 2.99999999999999988898e-01
0.400 4.00000000000000022204e-01
0.500 5.00000000000000000000e-01
0.600 5.99999999999999977796e-01
0.700 6.99999999999999955591e-01
0.800 8.00000000000000044409e-01
0.900 9.00000000000000022204e-01
The above solution not only performs the correct number of iterations, it doesn't accumulate error, so $i is always as correct as it can be.
In the second version, you print before the loop checks, i.e. it will even print the case which is already beyond the loop condition.
The fact that you get 1.0 as well as 1.1 is explained by 1.0 being slightly higher than the result of adding a lot of 0.1, because of floating point precision.
So what the loop actually sees is
0.9999 < 1.0 ? Yes, print and loop. Printing does some rounding, so what gets printed is 1.0.
1.0999 < 1.0 ? No, but print nevertheless (because loop checkign is done after printing.
So in order to solve, use the first version but start at 0.1 and check against 0.95.
$num = 0.1;
while ( $num < 0.95 ) {
print "$num \n";
$num = $num + 0.1;
}
Consider this:
use strict;
use warnings;
use v5.10;
my $num = 0;
while ($num < 1) {
say $num, " (", $num-1, ")";
$num += 0.1;
}
This outputs:
0 (-1)
0.1 (-0.9)
0.2 (-0.8)
0.3 (-0.7)
0.4 (-0.6)
0.5 (-0.5)
0.6 (-0.4)
0.7 (-0.3)
0.8 (-0.2)
0.9 (-0.1)
1 (-1.11022302462516e-16)
As you can see, due to floating point precision issues the number gained by repeated adding of 0.1 isn’t exactly one, but something slightly less than one, which is why the check succeeds one more time than expected.
Now add this to the imports at the top of the script:
use bignum;
And watch the output change:
0 (-1)
0.1 (-0.9)
0.2 (-0.8)
0.3 (-0.7)
0.4 (-0.6)
0.5 (-0.5)
0.6 (-0.4)
0.7 (-0.3)
0.8 (-0.2)
0.9 (-0.1)
This is because the computation is now done in a high-precision mode and the floating-point error no longer creeps in.
Usually, people get bitten a few times with floating point arithmetic and then learn to do the important part with integers or epsilons. Google, there’s plenty of resources on how to deal with floating point precision errors.

Perl script to convert a binary number to a decimal number

I have to write a Perl script that converts a binary number, specified as an
argument, to a decimal number. In the question there's a hint to use the reverse function.
We have to assume that the binary number is in this format
EDIT: This is what I've progressed to (note this is code from my textbook that I've messed with):
#!/usr/bin/perl
# dec2.pl: Converts decimal number to binary
#
die("No arguments\n") if ( $#ARGV == -1 ) ;
foreach $number (#ARGV) {
$original_number = $number ;
until ($number == 0 ) {
$bit = $number % 2 ;
unshift (#bit_arr, $bit) ;
$number = int($number / 2 );
}
$binary_number = join ("", #bit_arr) ;
print reverse ("The decimal number of $binary_number is $original_number\n");
$#bit_arr = -1;
}
When executed:
>./binary.pl 8
The decimal number of 1000 is 8
I don't know how to word it to make the program know to add up all of the 1's in the number that is inputted.
You could just use sprintf to do the converting for you...
sprintf("%d", 0b010101); # Binary string 010101 -> Decimal 21
sprintf("%b", 21) # Decimal 21 -> Binary 010101 string
Of course, you can also just eval a binary string with 0b in front to indicate binary:
my $binary_string = '010101';
my $decimal = eval("0b$binary"); # 21
You don't have to use reverse, but it makes it easy to think about the problem with respect to exponents and array indices.
use strict;
use warnings;
my $str = '111110100';
my #bits = reverse(split(//, $str));
my $sum = 0;
for my $i (0 .. $#bits) {
next unless $bits[$i];
$sum += 2 ** $i;
}
First of all, you are suppose to convert from a binary to decimal, not the other way around, which you means you take an input like $binary = '1011001';.
The first thing you need to do is obtain the individual bits (a0, a1, etc) from that. We're talking about splitting the string into its individual digits.
for my $bit (split(//, $binary)) {
...
}
That should be a great starting point. With that, you have all that you need to apply the following refactoring of the formula you posted:
n = ( ( ( ... )*2 + a2 )*2 + a1 )*2 + a0
[I have no idea why reverse would be recommended. It's possible to use it, but it's suboptimal.]

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.

Simple Perl For-Loop

I have a for loop and I want to increment the variable by 0.1 each time, however the value changes differently from the increment and I am unsure as to why.
I've simplified the for loop and it still gives a strange output:
for (my $t = 1000; $t < 1500 ;$t+=0.1) {
print "$t\n";
}
It prints:
1034.9
1035
1035.1
1035.2
1035.3
1035.4
1035.49999999999
1035.59999999999
1035.69999999999
1035.79999999999
1035.89999999999
1035.99999999999
1036.09999999999
1036.19999999999
1036.29999999999
[it then goes on like this to 1500]
I do not know where the decimal places are coming from. Is this a problem with my understanding of Perl?
Thanks in advance.
1/10 is a periodic number in binary like 1/3 is in decimal. It cannot be represented exactly as a floating point number.
$ perl -e'printf "%.20g\n", 0.1'
0.10000000000000001
Never compare a floating pointer number to another without involving a tolerance, and be wary of accumulation of error.
The simple solution here to to do the arithmetic using integers, and generate the floating point numbers when needed
for (my $tx10 = 10000; $tx10 < 15000; ++$tx10) {
my $t = $tx10/10;
print "$t\n";
}
which simplifies to
for my $tx10 (10000..14999) {
my $t = $tx10/10;
print "$t\n";
}
____ ____ ____
0.1 = 0.00011 0.4 = 0.0110 0.7 = 0.10110
____ ____
0.2 = 0.0011 0.5 = 0.1 0.8 = 0.11001
____ ____ ____
0.3 = 0.01001 0.6 = 0.1001 0.9 = 0.11100
for (my $t = 1000; $t < 1500 ;$t+=.1) {
printf("%.1f\n", $t);
}
Alternative:
for (10000..14999) {
my $t = $_/10;
print "$t\n";
}
Since 0.1 cannot be exactly specified in binary, rounding errors will accumulate in your code. In this answer, the amount always stays close enough to exact so that perl's internal number to string rounding will display the correct number. Lesson: use integers whenever possible.
To test for the condition
perl -le 'for (my $t = 1000.0; $t < 1500.0 ;$t+=0.1) { print $t}'| perl -n -e '($a)=/(\d$)/; print "fail $a $b $_" if ($a eq $b); $b=$a'
yet another way to fix it
perl -le 'for (my $t = 1000.0; $t < 1500.0 ;$t+=0.1) { $t=sprintf("%.1f", $t); print $t}'| perl -n -e '($a)=/(\d$)/; print "fail $a $b $_" if ($a eq $b); $b=$a'

check if a number is int or float

In perl, I want to check if the given variable holds a floating point number of not. To check this I am using,
my $Var = 0.02 # Floating point number
if (int($Var) != $Var) {
# floating point number
}
But the above code will not work for 0.0,
How can I achieve this?
This is a FAQ. See How do I determine whether a scalar is a number/whole/integer/float?
You can also use Scalar::Util::Numeric.
Interestingly, while
#!/usr/bin/perl
use strict; use warnings;
use Scalar::Util::Numeric qw(isint);
my $x = 0.0;
print "int\n" if isint $x;
prints int (not surprising if you look at the source code), based on #tchrist's comment, it should be possible to look at the information supplied in the variables structure to distinguish 0 from 0.0:
#!/usr/bin/perl
use strict; use warnings;
use Devel::Peek;
my $x = 0.0; print Dump $x;
my $y = 0; print Dump $y;
$y *= 1.1; print Dump $y;
Output:
SV = NV(0x18683cc) at 0x182c0fc
REFCNT = 1
FLAGS = (PADMY,NOK,pNOK)
NV = 0
SV = IV(0x182c198) at 0x182c19c
REFCNT = 1
FLAGS = (PADMY,IOK,pIOK)
IV = 0
SV = PVNV(0x397ac) at 0x182c19c
REFCNT = 1
FLAGS = (PADMY,NOK,pNOK)
IV = 0
NV = 0
PV = 0
It seems to me that the check would have to just look at if the NOK flag is set. It takes me ages to write even the simplest XS so I won't provide an implementation.
($Var - int($Var))?'float':'int'
You can use autobox::universal module wich is part of autobox.
#! /usr/bin/perl
use autobox::universal qw(type);
my $x = 42;
print type($x), "\n";;
$x = 42.0;
print type($x), "\n";
Output:
INTEGER
FLOAT
Why not use regex?
print "Is INT\n" if ($test =~/-*\d+/);
print "Is FLOAT\n" if ($test =~/-*\d+\.\d+/);
#$test = "1234" = INT
#$test = "12.3" = FLOAT
Re-edited 2019-12-24 to work correctly in Perl (MHN)
Perl5 return true or false if a variable string is a float:
You have to either roll your own by being clever with regexes and handle edge cases, or else defer to a 3rd party library and catch and suppress exceptions thrown by them.
sub does_this_variable_look_like_a_perl_float {
$number_of_arguments = 0 + #_;
#If you passed too many arguments, exit program, it's certainly not a float
if ($number_of_arguments != 1){
print "ArgumentException, you passed an incorrect number of parameters.\n";
return 0;
}
$variable = $_[0];
$first_argument = shift;
if ( ref(\$first_argument) eq 'ARRAY') {
#arrays are not floats
print("arrays are not floats");
return 0;
}
if ($variable =~ m/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){
return 1;
}
return 0;
}
sub is_convertable_to_float__casting_and_killing_canaries {
$number_of_arguments = 0 + #_;
if ($number_of_arguments != 1){
print "ArgumentException, you passed an incorrect number of parameters.\n";
return 0;
}
my $result = 0;
$variable = $_[0];
use Error qw(:try);
try {
use Scalar::Util qw(blessed dualvar isdual readonly refaddr reftype
tainted weaken isweak isvstring looks_like_number
set_prototype);
$numeric_code = looks_like_number( $variable );
if ($numeric_code != 0){
$result = 1;
}
else{
$result = 0;
}
}
catch Error with { $result = 0; };
return $result;
}
sub is_float {
#Darkwing Duck says "I like Perl5's duck typing, so it's a float when I
#look at it with my regex goggles, and it overlays yes"
return does_this_variable_look_like_a_perl_float(#_);
#Beta-7 insists on a practical solution, the variable is a float when I ask
#a 3rd party library if it is a float, and the 3rd party classifies it
#as a float, without any major malfunctions or trapped bubble up implimentationitis.
#return is_convertable_to_float__casting_and_killing_canaries(#_);
}
#NO:
print(is_float("")); #blankstring
print(is_float("yeah")); #strings with ascii letters
print(is_float(" ")); #space whitespace
print(is_float("\t\n")); #tabs and newlines
print(is_float(" 58 ")); #whitespace on either side
print(is_float('e')); #e by itself
print(is_float('0xf')); #0x hexidecimal
print(is_float('\xf')); #\x hexidecimal
print(is_float("1,234.56")); #commas as thousands separator
print(is_float("35,5")); #European, East oriental comma
print(is_float(undef)); #undef and variants
print(is_float("NaN")); #nan and variants
print(is_float("inf")); #nan and variants
print(is_float("infinity")); #inf and variants
print(is_float("null")); #null and variants
print(is_float("12.34.56")); #double dots
print(is_float("四")); #unicode, oriental japanese 4
print(is_float("#56")); #Pound sign
print(is_float("56%")); #percent sign
print(is_float("56^3")); #arithmatic expressions not interpreted
print(is_float("+1e1.5")); #decimals in exponent
print(is_float("+-1")); #make up your mind
print("\n");
#YES:
print(is_float(35)); #bare integer
print(is_float(35.5)); #decimal numeric, typical float
print(is_float(35.00000)); #superfluous zeros to the right
print(is_float("35")); #bare integer packaged as string
print(is_float(0)); #integer zero
print(is_float(07)); #leading zero makes for octal
print(is_float(000)); #stealth octal zero
print(is_float(-13)); #negative numbers
print(is_float(12e2)); #integers with e scientific notation
print(is_float(12.2e2)); #float with scientific notation
print(is_float(12.E4)); #float with e after period and scientific notation
print(is_float(.4)); #mantissa only
print(is_float(6e7777777777777)); #huge number
print(is_float(1.797693e+308)); #max value
print(is_float("0E0")); #zero in exponential
print(is_float(0**0)); #exponentiation
print(is_float("-5e-5")); #raise a negative to a negative
print(is_float("+5e+5")); #raise a positive with plus to a positive
print(is_float(0xfade)); #valid hexidecimal converted before pass by value
print(is_float(0b1100_0000)); #valid binary converted before pass by value
print("\n");
#ERROR:
print(is_float(5,6,7))
my #s = (10,20,30);
print(is_float(#s));
$arr = (5, 'foo', 7);
print(is_float($arr)); #an array containing integers and strings
print(is_float((1, 2, 3))); #error, array is not float
print(is_float(35,5)); #error, comma is not a decimal here, that's 2 parameters
Results of 'does_this_variable_look_like_a_perl_float():
0000000000000000000000
11111111111111111111
Results of 'is_convertable_to_float__casting_and_killing_canaries():
0000100000011100000000
11111111111111111111
I'm unsure if you are wishing to fish a number out of the variable or whether you wish to check if the variable IS (well, can be) a float or int.
However, to build on the regex example above, its mostly correct, minus a couple tweaks.
#original code snippet
print "Is INT\n" if ($test =~/-*\d+/);
print "Is FLOAT\n" if ($test =~/-*\d+\.\d+/);
#$test = "1234" = INT
#$test = "12.3" = FLOAT
if test is this however...
$test = "123.asd23aa"; # IS AN INT!!!!
try this instead:
$test = "123.asd23aa";
print "Is INT\n" if ($test =~/-*\d+/); #NO ANCHORS, -* allows for multiple --
print "Is INT\n" if ($test =~/^-?\d+$/); # WITH ANCHORS, fixed -?
print "Is FLOAT\n" if ($test =~/^-?\d*\.\d+$/); #AS ABOVE, with \d* instead of \d+ (floats need not have a leading 0 . can start with .5 or -.5 etc)
and here are some easy subs you can drop into whatever tool box you might have
sub isInt{
return ($_[0] =~/^-?\d+$/)?1:0;
}
sub isFloat{
return ($_[0] =~/^-?\d*\.\d+$/)?1:0;
}
sub isNum{
return (isInt($_[0]) || isFloat($_[0]) )?1:0;
}
This should do what you need:
my $var = 0.02 # Floating point number
if ($var > int($var)) {
# is floating point number because int() always rounds down
}
If you need to test for 0.00 in your application, you could use:
my $var = 0.00 # Floating point number
if ($var > int($var) || length($var) > length(int($var)) {
# is floating point number because int() always rounds down
# or string length is longer than integer string
}