Perl weird behaviour in number with decimal point - perl

So I was writing a perl program to do some calculation and
I had put a floating point number as
$x = 00.05;
if I print
print $x * 100.0;
It returns 500.0
But if I do
$x = 0.05;
print $x * 100.0;
it prints correctly 5.0;
What is this behaviour?
Is there any convention I have to obey that I am missing?

A leading zero means an octal constant, so when you do
my $x = 00.05;
you actually do string concatenation of two octal numbers:
my $x = 00 . 05; # The same as "0" . "5"
which gives you the string "05" and later you do
print $x * 100.0; # prints 500
since perl interprets as "05" as the number 5

Related

Perl sprintf integer issue

With this line, I expect the output 201
$ perl -e '$e = '2.01'; $c = sprintf("%d", $e * 100); print $c;'
But I get 200 instead. I don't understand why.
Because of the binary representation of a float number.
Try:
perl -e '$e = 2.01;printf("%2.25f\n",$e);'
Output:
2.0099999999999997868371793
The intetger part of this number multiplied by 100 gives 200
perl -e 'my $e = "2.01"; my $c = sprintf("%.0f", $e * 100); print $c;'
With %d, the integer is truncated.
And if you don't bother with printf :
perl -e 'my $e = "2.01"; my $c = $e * 100; print $c;'
Floating point representation has a limit to its accuracy, and the binary representation of 2.01 just happens to be fractionally less than 2.01.
The %d format conversion truncates the value to the next lowest value. It actually does a call to int, whose documentation says
You should not use this function for rounding: one because it truncates towards 0 , and two because machine representations of floating-point numbers can sometimes produce counterintuitive results.
And int(200.99999999999) is 200, which is what you are getting.
The canonical way in Perl to get the closest integer to a floating point value is to use the %f conversion with zero decimal points. So if you write instead
perl -e '$e = '2.01'; $c = sprintf("%.0f", $e * 100); print $c;'
you will get the output
200
Following document mentions about floating point and binary representation. This is from python.org, but it also works for perl.
https://docs.python.org/3/tutorial/floatingpoint.html
$ perl -e 'if (2.01 + 2.01 + 2.01 == 6.03) { print "yes" } else { print "no" }'
no
You can try using bignum pragma as a makeshift:
$ perl -Mbignum -e '$e = "2.01"; $c = sprintf("%d", $e * 100); print $c;'
201

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';

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
}

How do you round a floating point number in 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";'