Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Math::round not rounding correctly

by bulrush (Scribe)
on Dec 23, 2014 at 19:26 UTC ( #1111212=perlquestion: print w/replies, xml ) Need Help??
bulrush has asked for the wisdom of the Perl Monks concerning the following question:

Perl 5.8.8 on Redhat Linux

I'm doing "use Math::Round qw(:all)" and later in my program doing "$l=round($x)" where $x is 3461.5. $l ends up being 3461 when it should be 3462. How can I ensure Math::Round is being used?

I have not used Math::Round before. And there is no "round" in my utils include file or anywhere else, otherwise I'd get an error at some time.

Thanks.

p.s. I have tried using several rounding methods after doing some Googling and it looks like Math::Round would work best. I seem to have the most problems with numbers ending with .5.

EDIT: This gave me no error, but it also did not round correctly to 3462.

$x=$l1*$l2;
$l=Math::Round::round($x);
In my main program: The sprintf method doesn't round correctly in all cases, and neither does using POSIX ceil() and floor() in my main program. My test program works, but not my main perl program. So I was trying to ensure the Math::Round::round() function was being called.

EDIT 2: When the number is 3461.5 it should round to 3462 but rounds to 3461. How do I round this correctly? Try these test programs at the shell prompt. The first program rounds to 2 decimals. The second to 0 decimals to show the rounding problem. This is the actual calculation I'm having trouble with.

perl -e 'print sprintf("%.4f",(3010*1.15))."\n"' # Outputs 3461.5000
perl -e 'print sprintf("%.2f",(3010*1.15))."\n"' # Outputs 3461.50
perl -e 'print sprintf("%.0f",(3010*1.15))."\n"' # Outputs 3461

Replies are listed 'Best First'.
Re: Math::round not rounding correctly
by ikegami (Pope) on Dec 23, 2014 at 20:44 UTC
    15/100 is a periodic number in binary, so it can't be stored accurately as a float.
    ____ 1.15 base10 = 1.001001 base2

    This error causes 3010*1.15 to be equal to 3461.4999999999995, which Math::Round is correctly rounding to 3461.

    You need to incorporate a tolerance. Since 1/2 can be stored precisely in a floating point number (0.510 = 0.12), you can use the following:

    use feature qw( say ); use Math::Round qw( round ); my $x = 3010*1.15; say round($x); say round(round($x * 10**5) / 10**5); # 5 decimal places of precision
    3461 3462

    This is basically what tye did by stringifying the number, except he let Perl "choose" the factor.

Re: Math::round not rounding correctly
by Corion (Pope) on Dec 23, 2014 at 19:35 UTC

    Have you read the Standard Floating-Point Disclaimer, as is part of Math::Round?

    Your situation may be one where it applies.

    POSIX::ceil and POSIX::floor don't do rounding, they truncate towards an integer number. Maybe you can explain what results you expect and what results you get. Also, please supply code and data we can use to easily replicate your situation.

      Updated original post with edit 2.
Re: Math::round not rounding correctly (simple)
by tye (Sage) on Dec 23, 2014 at 20:01 UTC

    When the number is actually 3461.49999999999999382 it should be rounded to 3461 but might be displayed as simply "3461.5".

    Edit 1 in response to at least the second "edit" labeled "Edit 2" above (for sanity's sake, please stop changing the root of this conversation):

    perl -e 'print sprintf("%.20f",(3010*1.15))."\n"' # Outputs 3461.49999 +999999950000000

    QED. (Now, maybe you'll go read and try to understand the stuff about floating point that was linked to quite a while ago?)

    - tye        

      You got 3461.49999999999950000000? Because I didn't.

      What's your OS name and version? What's your perl version? Perhaps we have a difference of some math library or something.

        You got 3461.49999999999950000000? Because I didn't.

        What's your OS name and version? What's your perl version? Perhaps we have a difference of some math library or something.

        (Prior node quoted in entirety due to rash of prior "edit"s.)

        So I edited my prior node to correct my mis-paste. Clearly "%.4f" would not yield that many trailing digits. To get 20 trailing digits, I clearly had used "%.20f" (which is what my node now shows). (I also replied as edits are not so easy to notice, naturally.)

        Please try again.

        - tye        

Re: Math::round not rounding correctly
by MidLifeXis (Monsignor) on Dec 23, 2014 at 20:50 UTC

    In response to 3010*1.15: 1.15 is not a number that can be represented exactly in base-2, or as a sum of 1/(2**n) factors. Because of that, you will necessarily (given no access to a fractional number library of some sort) need to approximate the value when stored as a floating point. This is why your rounding looks weird. The closest you are able to get is:

    1 + (1/8) + (1/64) + (1/128) + (1/1024) + (1/2048) ...

    This basically goes for a ReallyLongTime™. Printf rounds to a specific number of digits unless told otherwise, as tye and others have illustrated. What Every Computer Scientist Should Know About Floating Point is a better explanation of this than I have time to give here, and should be required reading for any programming course dealing with numerical representation in hardware.

    --MidLifeXis

Re: Math::round not rounding correctly
by Laurent_R (Canon) on Dec 23, 2014 at 22:46 UTC
    There are two different issues. One is the general issue about floating point arithmetic, with the problems between binary and decimal representations of floats, well covered for example in the What Every Computer Scientist Should Know About Floating-Point Arithmetic document that has already been mentioned. But this is only part of the story.

    I have already stated that above, but no one seems to have noticed. The other problem is that the international IEEE standard for rounding numbers has enforced rules which are not what most people think. According to this international standard, the rule for true rounding is this: when you want to round a number with one decimal place to an integer, numbers whose decimal place is 5 will be rounded up when the previous digit is odd and down when the previous digit even. So that, when rounding to an integer, 1.5 and 2.5 should both be rounded to 2 .

    Consider for example this illustration of true rounding according to international standards:

    $ perl -e 'printf " %.1f => %.0f\n", $_, $_ for qw / .5 1.5 2.5 3.5 4 +.5 5.5 6.5 7.5 8.5 9.5/;' 0.5 => 0 1.5 => 2 2.5 => 2 3.5 => 4 4.5 => 4 5.5 => 6 6.5 => 6 7.5 => 8 8.5 => 8 9.5 => 10
    This is what is considered correct. This is the international standard, and this is what the standard C library implements for the round function, and this is what Perl is also doing, presumably because it is based on the C library. (I have already explained in my post above the rationale for this apparently pesky rule.)

    Now to your problem with 3461.5 and to what you have added in your successive edits of your post. If I want to round that number, I get this:

    $ perl -e 'printf " %.0f \n", 3461.5;' 3462 $ perl -e 'printf " %.0f \n", 3462.5;' 3462
    The last integer digit in 3461.5 is odd, so its gets rounded up. and 3462.5 is rounded down to the same number. Fair enough, that's the rule stated above.

    Now when you using your multiplication for creating 3461.5:

    $ perl -e 'printf " %.0f \n", 3010*1.15,' 3461
    Why? Well, if you want to know, try this:
    $ perl -e 'printf "%.15f \n", 3010*1.15,' 3461.499999999999545
    As you can see, the internal representation of 3010*1.15 is actually slightly smaller than 3461.5, so it should logically be rounded down. This is due to the fact that 1.15 expressed in binary representation is periodical, i.e. a number with an infinite number of digits (just like 1/3 in decimal representation: 0.333333333...). If our computers were using internal decimal representation with, say, 13 decimal digits, 1/3 would have to be stored, say, as 0,3333333333333, meaning that the ...333... of the 14th, 15th, 16th, etc. digits are rounded down. Similarly, in binary format, 1.15 is truncated of some of its digits after a certain number of "decimal" places, to that the multiplication results in a number smaller than 3461.5. BTW, to get a, idea of the decimal value of the internal binary representation of 1.15, just try the same thing:
    $ perl -e 'printf "%.19f \n", 1.15;' 1.1499999999999999112
    A near miss, but not quite exactly 1.15.

    And, by the way, just changing very slightly the numbers in your product yields the result you expect:

    $ perl -e 'printf " %.0f \n", 301*11.5;' 3462
    because 11.5 is not a periodical number in its binary representation. The difference between 301*11.5 and 3010*1.15 is:
    $ perl -E 'say 301*11.5 - 3010*1.15;' 4.54747350886464e-13

    Update: Corrected a typo in the second paragraph: "when the previous digit is odd" (and not "is off" as I had written). Thanks to N-Wing for picking it up.

Re: Math::round not rounding correctly
by Laurent_R (Canon) on Dec 23, 2014 at 20:00 UTC
    Asides from the general comment on floating-point arithmetic calculation, you should know that the international IEEE standard for rounding numbers is more complicated that what you probably expect. If you want to round a number with one decimal place to an integer, numbers whose decimal place is 5 will be rounded up or down, depending on whether the previous digit is odd or even. This is the international standard, and this is what the standard C library implements for the round function, and this is what Perl is also doing, presumably because it is based on the C library. The rational for that is that if you always round up numbers with one decimal place equal to 5, you get a bias upward in sums of large number of numbers. So that a number with one decimal place equal to 5 is sometimes rounded up and sometimes rounded down.
      Thank you, but if you look at my added test programs in Edit 2, you will see the number actually is "3461.5000", and it should be rounded to "3462".
Re: Math::round not rounding correctly
by jmlynesjr (Hermit) on Dec 23, 2014 at 20:03 UTC

    In the old FORTRAN days we added .5 then truncated.

    James

    There's never enough time to do it right, but always enough time to do it over...

      That won't help. It will also round 3461.4999999999995 down.

Re: Math::round not rounding correctly
by Anonymous Monk on Dec 23, 2014 at 20:15 UTC
    tye is talking about that:
    $ perl -e 'printf "%.10f\n%.30f\n", 3010*1.15, 3010*1.15' 3461.5000000000 3461.499999999999545252649113535881
    So it seems a quick and dirty hack would be to sprintf the number and then round the string? :)

      A better "work-around" to what is arguably "correct" behavior would be:

      $l=Math::Round::round("$x"); # Math::Round::round($x) # vs. this

      If what you care about is how the rounding compares to the string output for the number, then what you should be rounding is the string output, of course.

      - tye        

      Hmm. Well this seemed to work in this case. I'll test it out.
      perl -e 'print int(sprintf("%.10f",(3010*1.15))+0.5)."\n"'
      
Re: Math::round not rounding correctly
by BillKSmith (Vicar) on Dec 23, 2014 at 22:38 UTC
    Any rounding discipline is a compromise. The IEEE rounding is at least "good enough" for almost all applications. If you are certain that it is not for yours, you probably know enough to write a special one for this application only. What about negative numbers? Have you considered the floating point issue correctly?
    Bill

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1111212]
Approved by Corion
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2018-02-24 06:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    When it is dark outside I am happiest to see ...














    Results (310 votes). Check out past polls.

    Notices?