On Mon, Apr 26, 2010 at 3:57 PM, Shlomi Fish <shlo...@iglu.org.il> wrote:

> On Monday 26 Apr 2010 14:05:16 Shlomi Fish wrote:
> > Hi Rob,
> >
> > On Monday 26 Apr 2010 13:38:07 Rob Coops wrote:
> > > Hi list,
> > >
> > > I am just wondering if I sumbled upon an error in perl an error in my
> > > logic or somehtign else see the below perl one liners
> > >
> > > $ perl -e '$n = 0.945; $r = sprintf("%.2f", $n); print "$r\n";'
> > > 0.94
> > >
> > > $ perl -e '$n = 0.9451; $r = sprintf("%.2f", $n); print "$r\n";'
> > > 0.95
> > >
> > > $ perl -e '$n = 0.9450; $r = sprintf("%.2f", $n); print "$r\n";'
> > > 0.94
> > >
> > > $ perl -e '$n = 0.94500; $r = sprintf("%.2f", $n); print "$r\n";'
> > > 0.94
> > >
> > > $ perl -e '$n = 0.945001; $r = sprintf("%.2f", $n); print "$r\n";'
> > > 0.95
> > >
> > > Now I expected perl to round the number: 0.945 to 0.95 and not to 0.94
> > > like it is doing. I have no idea why it is doing this but I sure don't
> > > like it. The problem is I have to do some VAT calculations on thousands
> > > of invoices and invoice elements. I found that in rare cases I'm of by
> 1
> > > cent or even 3 cents if there are lots of elements. Going over the
> logic
> > > I ran into this problem.
> > >
> > > Cany anyone advise me how to deal with this as I need to find a way to
> > > round correctly in all cases not just in cases where Perl decides it is
> a
> > > good idea to do so. :-)
> >
> > The problem is likely caused by the fact that floating-point numbers in
> > most modern computers are represented by using binary digits (and binary
> > fractions) instead of decimal ones. So there may be rounding errors. As a
> > result, it is not advisable to use the regular floating-point numbers for
> > dealing with monetary values. Some of the options you may wish to
> consider
> > instead are:
> >
> > 1. Use a rational library for computation such as Math::BigRat.
> >
> > 2. Use a binary-coded-decimal (BCD) system. Last time I investigated I
> > couldn't find one for Perl, though , and I've misplaced the link to the
> > downloadable software library at IBM for that. (Though Python has
> something
> > like that in pure-Python)
> >
> > 3. Work always in the smallest unit - cents or whatever.
> >
>
> I eventually forgot to add the link to this article:
>
> http://docs.sun.com/source/806-3568/ncg_goldberg.html
>
> "What Every Computer Scientist Should Know About Floating-Point
> Arithmetic".
> It's kinda long, but I mostly enjoyed it and found it enlightening.
>
> Regards,
>
>        Shlomi Fish
>
> --
> -----------------------------------------------------------------
> Shlomi Fish       http://www.shlomifish.org/
> My Aphorisms - http://www.shlomifish.org/humour.html
>
> God considered inflicting XSLT as the tenth plague of Egypt, but then
> decided against it because he thought it would be too evil.
>
> Please reply to list if it's a mailing list post - http://shlom.in/reply .
>

Thank you all that replied, in the end I copied the rules I learned in
elementary school about how to round a number to two decimals. I'm sure it
is a horrible hack and can be done a lot faster, but with a country not
being able to process their invoices into the accounting system this was the
best way I could come up with that didn't in some cases result in a error.
I am not using any of the suggested modules because the production system is
outside of my control and getting a module installed can take days if not
longer due to processes procedures forms and so on. The country has only a
few days left till the end of the month by which time this should absolutely
be working.

This is the routine as it looks now (I would be happy to hear about errors
or improvements that you might see)
sub round {
 my $number = shift;
 if ( $number == 0 ) { return 0.00; }
 if ( length( $number ) <= 4 ) { return $number; }
 my $int = int( $number ); # Get the whole number
 my $dec = $number - $int; # Get the decimals only

 $dec = sprintf( "%.15f", $dec ) * 1000000000000000;

 my @digits = split( //, $dec );
 my @reverse_digits = reverse( @digits );
 my $number_of_digits = $#reverse_digits - 1;

 my $remember = 0;
 my $tracker = 0;
 for( my $x = 0; $x <= $number_of_digits; $x++ ) {
  $reverse_digits[$x] = $reverse_digits[$x] + $remember;
  my $result = $reverse_digits[$x] + 5;
  if ( $result >= 10 ) {
   $remember = 1;
  } else {
   $remember = 0;
  }
  $tracker = $x;
 }

 if ( $reverse_digits[$tracker] >= 10 ) {
  $reverse_digits[$tracker] = $reverse_digits[$tracker] - 10;
  $reverse_digits[$tracker + 1]++;
  if ( $reverse_digits[$tracker + 1] == 10 ) {
    $reverse_digits[$tracker + 1] = 0;
    $int++;
  }
 }
 my @reordered_digits = reverse(@reverse_digits);
 splice( @reordered_digits, 2 );
 my $decimal_amount = join( '', @reordered_digits );
 return "$int.$decimal_amount";
}

Again, thanks for all the help and suggestions,

Rob

Reply via email to