in reply to strange rounding behaviour

You've found the hidden indeterminacy of floating-point numbers.

Because of the way that most machines handle floating-points, they're frequently not what they say they are. From PerlFaq 4:

Internally, your computer represents floating-point numbers in binary. Floating-point numbers read in from a file or appearing as literals in your program are converted from their decimal floating-point representation (eg, 19.95) to the internal binary representation.

However, 19.95 can't be precisely represented as a binary floating-point number, just like 1/3 can't be exactly represented as a decimal floating-point number. The computer's binary representation of 19.95, therefore, isn't exactly 19.95.

If you really, really need exact handling of floating-point numbers, you can (at a good deal of computational expense) use Math::BigFloat. Numbers which use Math::BigFloat are internally handled as strings, so you won't get the strange rounding errors you're experiencing. Plus, it provides a bround method that does the exact rounding you want.


Replies are listed 'Best First'.
Re: Re: strange rounding behaviour
by riffraff (Pilgrim) on May 18, 2001 at 20:35 UTC
    Or, if you know exactly how many decimal places you need, you can scale it up by that magnitude, and use ints instead.

    I.e., if you need 2 decimal places, use ints * 100, and print like this:

    print ($a/100),".","($a%100),"\n";

      I had written a well thought out reply to this, but then by browser crashed, so I'll just provide some points on why this script fails:

    • First of all, it's syntactically invalid (this, I assume, is just a type): you provide a " before ($a%100), I assume that isn't meant to be there. Also, you need to place parentheses around your print call so all of the items you want to send go to print.
    • Second, It doesn't account for the fact that the computer cannot store the floating point numbers accurately.
    • Third, it produces output like this: (when the typos are corrected)
      1.23.23 4.345.34 5.523.52
      You see, you print ($a/100), but if that is a decimal, then it will print (decimal and all!), and then the added decimal will print, not what you want.
    • Last, it doesn't round. The middle number should read 4.35 (after being rounded), but your code (ignoring the extra decimal and just reading from the last), makes it 4.34.

      The 15 year old, freshman programmer,
      Stephen Rawls
        Right, the " was a typo, and, as I mentioned, if you know the amount of decimal places you need, you scale up, and get rid of decimals entirely. That way you don't have rounding errors due to the inaccuracy of IEEE floating point representation, and it is quite a bit faster (well, it was way back when; I'm not sure now)

        And, yes, you are correct, it doesn't round. I wasn't giving a whole block of code to use, just a quick bit to give an example on how it would be done.

        I used to do this all the time, about 15 years ago, when most computers did not have FPU's, and were much more inaccurate.