Code in Perl for pi calculation - perl

Could someone explain step by step this code in Perl:
$.=.5;
$\=2/$.++-$\for$...1e6;
print

Add white space:
$. = .5;
$\ = (2 / $.++) - $\ for $. .. 1e6;
Substitute with saner var names:
$x = .5;
$pi = (2 / $x++) - $pi for $x .. 1e6;
Don't use the same var for two purposes:
$x = .5;
$pi = (2 / $x++) - $pi for 0 .. 1e6;
Convert to formula:
pi = 2/1000000.5 - ( ... - (2/2.5 - (2/1.5 - (2/0.5))))
= 2/1000000.5 - ... + 2/2.5 - 2/1.5 + 2/0.5
= 4/2000001 - ... + 4/5 - 4/3 + 4/1
= 4/1 - 4/3 + 4/5 - ... + 4/2000001
which is listed in Wikipedia as the Gregory–Leibniz series.
print; is print($_);, which prints $_ (unused, so undef, which stringifies to empty string) followed by $\ (which is normally an empty string, but is used as the accumulator in the original code).

Let's start first by breaking the code down into something more readable with some white-space. White space is relatively unimportant in Perl.
$. = .5;
$\ = 2 / $.++ - $\ for $. .. 1e6;
print
This code is making use of Perl's special built-in global variables to make things look short (man perlvar for more details on the specific variables this code is using). Let's make it more readable by replacing them with names that are human readable.
$denominator = .5;
$pi = 2 / $denominator++ - $pi for $denominator .. 1e6
print $pi
And just to help, let's move the inline for loop into a more verbose loop.
$denominator = .5;
foreach my $i ($denominator .. 1e6) {
$pi = 2 / $denominator - $pi;
$denominator += 1;
}
print $pi
So, this makes it a bit easier to decipher, and it's a standard convergence routine for calculating pi. There's a number of different ones, but basically, if you step through it, you'll see that it converges to the famous 3.14159... The denominator is okay to use as the first element of the sequence generator because it will floor to the integer 0.
Pass 1: 2 / 0.5 - 0 = 4
Pass 2: 2 / 1.5 - 4 = -2.666...
Pass 3: 2 / 2.5 - (-2.666...) = 3.4666...
Pass 4: 2 / 3.5 - 3.4666... = -2.895...
Pass 5: 2 / 4.5 - (-2.895...) = 3.339...
You get the idea. The code runs through 1,000,001 passes to reach the final approximation. As long as it runs an odd number of approximations, it should converge to pi (even numbered approximations converge to negative pi).
The reason the print statement works is that $\ is the output record separator. print by itself will print $_, the default variable, which will be empty. The value of pi will be in the $\ value, which will effectively be the line ending, so it prints out a blank value followed by the calculated value of pi (no traditional line ending).

Wow, that's a beauty. Someone was trying to make that look as convoluted as possible. Here's a more readable version:
my $pi = 0;
for (my $i=0.5; $i < 1e6+1; $i++)
{
$pi=2/$i - $pi;
}
print $pi."\n";
This is the mathematical representation of the Gregory-Leibnitz Formula
The original version uses a few Perl special variables to confuse you. The $. is entirely unnecessary here and purely for obfuscation. The $\ is the output record separator. That means this variable will be appended after a print statement. Therefore, the single print at the end simply prints the result of the calculation that was stored in $\. Finally, the code uses a post-fix for loop and the .. operator instead of the more explicit for loop in my version. I hope that clears things up a bit.

This can be rewritten as:
use strict;
use warnings;
my $divisor = 0.5;
my $pi = 0;
for my $i (0..1e6)
{
$pi = 2 / $divisor++ - $pi
}
print "$pi\n";
So it uses some mathematical series expansion to calculate pi (See #ikegami's answer)

Related

perl - short factorial calculator returning long strings of 1's

I'm trying to make a program that calculates the factorial of a number. I'm not very familiar with perl, so I think I'm missing some grammar rule.
When I enter 5 the program should return 120. Instead it returns a few dozen 1's. When I try other numbers I still get 1's, but more or less of them depending on if I enter a higher or lower number.
Here's my code:
print"enter a positive # more than 0: \n";
$num = <STDIN>;
$fact = 1;
while($num>1)
(
$fact = $fact x $num;
$num = $num - 1;
)
print $fact;
system("pause");
This is my first post to stack Overflow, so I hope I obeyed all theposting rules.
The problem is this line:
$fact = $fact x $num;
x is not the multiplication operator in Perl. It is used to repeat things. 1 x 5 will produce "11111".
Instead you want *.
$fact = $fact * $num;
Which can be written using *=.
$fact *= $num;
A few other issues...
Get used to strict and warnings now. By default, Perl will let you use variables without declaring them. They are global, which is bad for reasons you'll learn later. Right now it means if you have a typo in a variable name like $face Perl won't tell you about it.
Looping over a list of numbers is better done with a for loop over a range using ...
# Loop from 1 to $num setting $factor each time.
for my $factor (1..$num) {
$fact *= $factor;
}
Instead of using a system call to pause the program, use sleep.

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.

Perl - Returning the maximum value in a data set

I have never delved into the world of Perl before and I find it pretty confusing and could use some help. In the code below the calc() section returns a running average of an'input' over 'count' samples. I would like to modify that so the calc() returns the maximum value within the sample set. Thanks in advance for the help!
sub calc
{
my ($this, $dim, $input, $count) = #_;
if ($count < 1)
{
warn "count=$count is less than 1.";
return undef;
}
my $inputsum_in = $this->{inputsum};
my ($inputcumsum, $inputsum_out) = PDL::CumulativeSumOver2($input, $inputsum_in);
my $inputdelay = $this->delay('inputhistory', $input);
my $inputdelaysum_in = $this->{inputdelaysum};
my ($inputdelaycumsum, $inputdelaysum_out) = PDL::CumulativeSumOver2($inputdelay, $inputdelaysum_in);
$this->{inputsum} = $inputsum_out;
$this->{inputdelaysum} = $inputdelaysum_out;
my $sampleno = $this->{sampleno};
my $divider = $count;
if($sampleno < $count)
{
my $last = $dim - 1;
$divider = sequence($dim) + ($sampleno + 1);
my $start = $count - $sampleno;
$divider->slice("$start:$last") .= $count if $start <= $last;
$this->{sampleno} = $sampleno + $dim;
}
return ($inputcumsum - $inputdelaycumsum) / $divider;
}
How about
$max = max($input);
PDL Primitives
If you want to find the maximum of a certain list of values, you do not need to write your own subroutine. There is already a function that comes shipped with perl v5.7.3 or higher:
use List::Util qw(max); # core module since v5.7.3
use strict;
use warnings;
print max(1 .. 10); # prints 10
EDIT: Here is the loop I take it you need.
Read input data from sensor
append new data to stored data
Throw away excess data
Evaluate
Here's how I'd do it.
my $storedData = pdl;
# $storedData is now a vector containing one element, 0
while (! stopCondition()) {
my $input = readSensorData(); # step 1
$storedData = $storedData->append($input); # step 2
if ($storedData->nelem > $count) { # step 3
$storedData = $storedData->slice("-$count:-1");
# note that -1 points to the last element in a piddle and -X refers to
# the element X-1 away from the end (true for piddles and native arrays)
}
my ($max, $min) = evaluate($storedData); # step 4
}
I'm not sure if this answers your question, but your comment below seems pretty different from the question you have above. Consider editing the above to better reflect what you're having trouble with or asking a new question.
An easy way to get a running average is with a finite impulse response filter, aka convolution. Convolve any signal with a (normalized) rectangular impulse and you get running average.
my $filter = ones($count) / $count;
my $runningAve = convolveND($input, $filter);
my $max = $runningAve->max`;
Or in one line
my $max = convolveND($input, ones($count) / $count)->max;
convolveND is documented here.
There is one thing to be careful of with this method, which is that the values at the beginning and end of the $runningAve piddle aren't really running averages. To ensure that the output is the same size as the input convolveND (by default) effectively concatenates zeroes to the beginning and end of the input, the result being that the first and last few elements of $runningAve are lower than actual running averages. (Note that a running average should have N - (window - 1) elements in principle, N being the size of $input.) Since these "bad" values will necessarily be lower than the actual running average values, they won't disturb the maximum that you want. (Re "by default": convolveND has other ways of handling edges, as you will see in the documentation linked to above.)
(NB: I am not a PDL expert. There may be a cheaper way to get the running average that's cheaper than convolveND, something like $ra = $input->range(...)->sumover(0) / $count, but I don't know what you'd put in the ... and the above is readable. See also http://search.cpan.org/~jlapeyre/PDL-DSP-Iir-0.002/README.pod#moving_average)

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