Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Unwanted implicit conversion to float

by turnstep (Parson)
on Mar 12, 2001 at 22:32 UTC ( [id://63883]=perlquestion: print w/replies, xml ) Need Help??

turnstep has asked for the wisdom of the Perl Monks concerning the following question:

A strange thing has cropped up in my code. Here's the relevant snippet, with some debugging added:

my $val=0; for (@rows) { if (/([\d.]+)/) { $val += $1; warn "<LI>Added $1 from $_ and set it to $val\n"; } }

The row values are mostly dollar-sign formatted, as you can see from the sample output. It gets funky at one particular spot:

  • Added 105.25 from $105.25 and set it to 105.25
  • Added 105.25 from $105.25 and set it to 210.5
  • Added 105.25 from $105.25 and set it to 315.75
  • ....many rows later...
  • Added 68.90 from $68.90 and set it to 6257.65
  • Added 33.40 from $33.40 and set it to 6291.04999999999
  • Added 100.90 from $100.90 and set it to 6391.94999999999
  • ..many rows later...
  • Added 153.70 from $153.70 and set it to 9897.64999999999
  • Added 102.70 from $102.70 and set it to 10000.35
  • Added 128.80 from $128.80 and set it to 10129.15

I can guess that perl is setting the match in the regular expression as a float, because of the decimal place, but what cause it to suddenly decide that 6257.65 plus 33.40 is 6291.04999999999??

Any workarounds welcome, too, although the actual code is not as simple as the example above.

Replies are listed 'Best First'.
Re: Unwanted implicit conversion to float
by merlyn (Sage) on Mar 12, 2001 at 22:37 UTC
    You've apparently not learned that (1/10) cannot be precisely represented in binary floating point. It's an infinite repeating expansion, which has to be truncated at some point (heh). Multiplying that by 10 does not yield 1.0 any more.

    If you want precise pennies, you must count in pennies (using integer values for pennies).

    -- Randal L. Schwartz, Perl hacker

      I'm curious... I recall this being a bigger problem in older versions of Perl and patches being applied such that Perl became smart enough to avoid including "9999999" or "0000001" on the end of floating point numbers.

      So is this a case where round-off is making the result "a bit off" from the closest possible value and this prevents the "smarter" code from avoiding the extra trailing digits? I wouldn't think that adding "1" would cause that problem.

      Update: Okay, we're doing a running total here so there is round-off involved. Sorry for the confusion.

              - tye (but my friends call me "Tye")
      *cry* sometimes ignorance is bliss...

      who wants the bill for the head imprint in my desk? You or turnstep ? :)
Re: Unwanted implicit conversion to float
by AgentM (Curate) on Mar 12, 2001 at 22:42 UTC
    This is something very low-level. It has to do with the IEEE representation of a float/double. One thing you might try is multiplying each number by one hundred, using integer, perform your calculation, and dividing back by one hundred. You might also try a different rounding mechanism such as POSIX::ceil or floor. Only use the number of digits of precision you need, "convert" that into an integer, perform the addition, and "convert" back.

    What I don't understand is your title. If you start with floats, then you'll end with floats.

    AgentM Systems nor Nasca Enterprises nor Bone::Easy nor Macperl is responsible for the comments made by AgentM. Remember, you can build any logical system with NOR.
(Ovid) Re: Unwanted implicit conversion to float
by Ovid (Cardinal) on Mar 12, 2001 at 22:49 UTC
    I suppose there are a variety of ways to handle this. One of the most straightforward would be the following:
    $val += sprintf( "%.2f", $1 );
    I think that should take care of that. My only concern would be the potential for rounding errors, if any of the numbers that you are matching against have more than two digits after the decimal point. In that case, you might want to just do the sprintf at the end.

    If anyone sees a problem with this approach, speak up! I've used it before and would love to know if I boo-booed.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

      I see just a (maybe null) risk in the fact that you still add 2 floating numbers, even if $1 has been rounded to 2 digits after the decimal point. So something weird can still happen with the result. After all turnstep's original code only added such numbers. So you could end up every now and then with "long numbers".

      Doing $val= sprintf( "%.2f", $val + $1); removes the problem, $val is _always_ rounded properly. The cleanest way is of course to keep $val as is and to round it only when printing it.

      This may just be nitpicking but as I have no idea of the conditions that create the problem and it is probably quite hard to reproduce, I would not take any chance.

        The cleanest way is of course to keep $val as is and to round it only when printing it.

        Perhaps I misunderstand what you mean by "clean", but the safest way to deal with money is to round the internal number any time you print something out1, and use that rounded number for all future calculations. If you are doing calculations with only whole-cent amounts (such as adding up a bunch of deposits), then you should round after every operation (or at least frequently) to prevent tiny round-off errors from accumulating such that your final total is off by one cent when it is finally rounded.

        If you are doing calculations with fractional cents, then you need to define exactly where the rounding occurs (for example, interest is applied one a month and is always rounded at that time).

        1 Update: The reason I say "every time you print something out" is that you (almost always) need to round if the value is being reported to the user. You can (and often should) do internal, intermediate calculations in fractional cents, but whenever you report [or could report] an amount to the user, you need to round your internal representation of that amount the same way you [would] round the amount you [could] report.

                - tye (but my friends call me "Tye")
Re: Unwanted implicit conversion to float
by larryl (Monk) on Mar 12, 2001 at 23:17 UTC

    You might want to look at using a module like John Peacock's Math::Currency, "Exact Currency Math with Formatting and Rounding".

Re: Unwanted implicit conversion to float
by mirod (Canon) on Mar 12, 2001 at 22:48 UTC

    In this case sprintf is your friend (and coming from a C background an asset). It might slow you down somehow but if you throw in $val= sprintf( "%f6.2", $val); before using $val, you will get back the proper precision.

    Here is an example:

    #!/usr/bin/perl -w use strict; my @rows=<DATA>; my $val=0; for (@rows) { chomp; if (/([\d.]+)/) { $val += $1; $val= sprintf( "%6.2f", $val); warn "Added $1 from $_ and set it to $val\n"; } } __DATA__ we get to $6257.65 now we add $33.4099999999999999 we add $37.24

    outputs:

    Added 6257.65 from we get to $6257.65 and set it to 6257.65
    Added 33.4099999999999999 from now we add $33.4099999999999999 and set it to 6291.06
    Added 37.24 from we add $37.24 and set it to 6328.30 
Re: Unwanted implicit conversion to float
by Trimbach (Curate) on Mar 12, 2001 at 22:39 UTC
    I've run across the same sort of thing, adding and subtracting monetary amounts like that. The best way of dealing with I found was to let Perl handle the internal math however it wanted (if it wants to make something $6391.94999999999 who am I to argue?) and control the output using sprintf. This lets Perl determine precision for calculation while you have total control over precision of display. And all it right in the world...

    Gary Blackburn
    Trained Killer

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://63883]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (10)
As of 2024-03-28 10:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found