Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Rounding over numbers

by Anonymous Monk
on Feb 15, 2016 at 04:32 UTC ( [id://1155225]=perlquestion: print w/replies, xml ) Need Help??

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

Hi all,

Is there a quick way to round off numbers as follows:

1.24 to 1.20 13.25 to 13.30 2.90 to 2.90 2.99 to 3.00 2.92 to 2.90 14 to 14.00

Thanks in anticipation :)

Replies are listed 'Best First'.
Re: Rounding over numbers
by kevbot (Vicar) on Feb 15, 2016 at 05:45 UTC
    Here's one way to do it, with help from the Number::Format module.
    #!/usr/bin/env perl use strict; use warnings; use Number::Format; while(<DATA>) { chomp $_; my $x = new Number::Format; my $formatted = $x->format_number($_, 1, 1); $formatted = sprintf("%.2f", $formatted); print "$_ -> $formatted\n"; } exit; __DATA__ 1.24 13.25 2.90 2.99 2.92 14
    Output:
    1.24 -> 1.20 13.25 -> 13.30 2.90 -> 2.90 2.99 -> 3.00 2.92 -> 2.90 14 -> 14.00
Re: Rounding over numbers
by Athanasius (Archbishop) on Feb 15, 2016 at 06:06 UTC

    TMTOWTDI. Here’s a general approach using sprintf:

    #! perl use strict; use warnings; use Test::More; while (<DATA>) { my ($decimals, $source, $target) = / (\d+) \D+ ([\d.]+) \D+ ([\d.] ++) /x; my $result = round($source, $decimals); is($result, $target, "round $source to $target"); } done_testing(); sub round { my ($num, $decimals) = @_; return sprintf("%.*f0", $decimals, $num); } __DATA__ 1 1.24 to 1.20 1 13.25 to 13.30 1 2.90 to 2.90 1 2.99 to 3.00 1 2.92 to 2.90 1 14 to 14.00 5 1.2345678 to 1.234570

    Output:

    16:05 >perl 1544_SoPW.pl ok 1 - round 1.24 to 1.20 ok 2 - round 13.25 to 13.30 ok 3 - round 2.90 to 2.90 ok 4 - round 2.99 to 3.00 ok 5 - round 2.92 to 2.90 ok 6 - round 14 to 14.00 ok 7 - round 1.2345678 to 1.234570 1..7 16:05 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Using your code on my system, I get a test failure for 13.25:
      ok 1 - round 1.24 to 1.20 not ok 2 - round 13.25 to 13.30 # Failed test 'round 13.25 to 13.30' # at r2.pl line 12. # got: '13.20' # expected: '13.30' ok 3 - round 2.90 to 2.90 ok 4 - round 2.99 to 3.00 ok 5 - round 2.92 to 2.90 ok 6 - round 14 to 14.00 ok 7 - round 1.2345678 to 1.234570 1..7 # Looks like you failed 1 test of 7.
        Not very surprising, most probably floating point arithmetic inaccuracy again. When you assign 13.25 to a variable, you can't know for sure if the number stored internally will be something like 13.2499999999976 or rather something like 13.25000000000012. This will depend on the machine architecture and a few of other factors.

        There may also be a difference with the libraries used. IEEE recommendation is to round the trailing "5" digit up or down depending on whether the previous digit is odd or even, but some libraries may still round up systematically.

        Can you perhaps try again with 1325/100 to see if it makes a difference?

        Interesting. What is your system?

        For the record, I’m running Strawberry Perl 5.22.1 under Windows 8.1 (64-bit). But it also tests successfully on my Strawberry Perl versions 5.12.3, 5.14.4, 5.18.2, and 5.22.0. The first two of these are 32-bit builds; the rest are 64-bit.

        Update: Added info on 32- and 64-bit perl builds.

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Rounding over numbers
by Discipulus (Canon) on Feb 15, 2016 at 08:45 UTC
    Here my brutal attempt translating in perl what i'd say in plain language. Note the *10 at the begin and the /10 at the end: they make your life easier confining the problem to one digit only.
    use strict; use warnings; print map { my ($int,$dec)=split /\./,$_*10; $dec = defined $dec && $dec >= 5 ? 1 : 0; sprintf("%.2f\n", ($int+$dec)/10); } (<DATA>); __DATA__ 1.24 13.25 2.90 2.99 2.92 14 # OUT 1.20 13.30 2.90 3.00 2.90 14.00

    HtH

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Rounding over numbers
by Athanasius (Archbishop) on Feb 15, 2016 at 12:05 UTC

    I’ve been thinking over this question in light of the notorious floating-point problems highlighted by kevbot and Laurent_R above. It does seem that kevbot’s recourse to Number::Format is the better option. Here’s the relevant code from the round subroutine in that module (omitting parameter checks, negative number handling, etc.):

    sub round { my ($self, $number, $precision) = ...; ... my $multiplier = (10 ** $precision); my $result = abs($number); my $product = $result * $multiplier; ... # We need to add 1e-14 to avoid some rounding errors due to the # way floating point numbers work - see string-eq test in t/round. +t $result = int($product + .5 + 1e-14) / $multiplier; ... return $result; }

    This is, I think, the correct approach. But it still leaves me wondering...

    • Since $result is returned as a floating point number (generated by a division operation), isn’t there still the possiblity that it will be slightly lower than it should be? In which case, the accuracy of the call could in part depend on the way the return value is used by the calling code?
    • That “epsilon” value of 1e-14 worries me! Common sense suggests that if a fudge factor is needed to make some tests work when they should, then that same fudge factor will make other tests fail when they shouldn’t. Or is this paranoia?

    I’m thinking the only foolproof method may be to avoid the use of floating-point numbers altogether by doing the calculation with integers, and inserting the decimal point only when the calculation is complete. Is there a module which takes this approach? So far, I haven’t found one (but I may well be looking in the wrong places).

    Any ideas?

    Update: Corrected typos in the second bullet point.

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      That “epsilon” value of 1e-14 worries me! Common sense suggests that if a fudge factor is needed to some tests work when they should, then that same fudge factor will make other tests fail when it should’t. Or is this paranoia?
      This also worries me. I can't believe that this will solve the problem in all cases.
      I’m thinking the only foolproof method may be to avoid the use of floating-point numbers altogether by doing the calculation with integers, and inserting the decimal point only when the calculation is complete. Is there a module which takes this approach? So far, I haven’t found one (but I may well be looking in the wrong places).
      It does not seem that BigRat, BigInt, BigNum and the like are doing that.

      But that is what Perl 6 is doing with the Rat type, which consists of a pair of integer numbers representing separately the numerator and the denominator, so that 13.25 would be represented as (1325, 100), or perhaps more probably (53, 4). I can't check right now, but maybe one of the Perl 6 modules (I mean the Perl 5 modules developed over the years to simulate and test the Perl 6 concepts) is doing that.

Re: Rounding over numbers
by hippo (Bishop) on Feb 15, 2016 at 09:02 UTC

    Yes, the quick way is to read the FAQ.

Re: Rounding over numbers [Perl6]
by u65 (Chaplain) on Feb 15, 2016 at 16:20 UTC

    Update: Added "Perl6" tag to end of subject.

    On my Debian 7, 64-bit system, with Perl 5.14 I see the following results

    $ perl -e 'printf "13.24 rounds to %.*f0\n", 1, 13.24'; 13.24 rounds to 13.20 $ perl -e 'printf "13.25 rounds to %.*f0\n", 1, 13.25'; 13.25 rounds to 13.20 $ perl -e 'printf "13.26 rounds to %.*f0\n", 1, 13.26'; 13.26 rounds to 13.30

    and using the Perl 6 REPL shows

    > say sprintf("13.24 rounds to %.*f0", 1, 13.24); 13.24 rounds to 13.20 > say sprintf("13.25 rounds to %.*f0", 1, 13.25); 13.25 rounds to 13.30 > say sprintf("13.26 rounds to %.*f0", 1, 13.26); 13.26 rounds to 13.30

    which I believe is due at least partly to Perl 6's use of rational numbers--a major feature.

Re: Rounding over numbers
by Anonymous Monk on Feb 15, 2016 at 12:31 UTC

    Everyone here seems to be assuming your inputs are floating-point numbers, meaning you have to deal the issues connected to that, but are they really? Do you happen to be getting them as strings or some other format? Also, will your inputs only have a max. of two decimal places?

      Yes, all inputs will have been formatted to 2 decimal places before the rounding. The last example should have been 14.00.

Re: Rounding over numbers
by karlgoethebier (Abbot) on Feb 15, 2016 at 20:44 UTC

    Math::Round?

    «The Crux of the Biscuit is the Apostrophe»

Re: Rounding over numbers
by BillKSmith (Monsignor) on Feb 15, 2016 at 21:50 UTC
    There are at least six ways to handle a "final 5" (toward positive infinity, toward negative infinity, toward even, toward odd, toward zero, away from zero). And this is before you even consider floating point issues. Fortunately, for most applications, it does not matter which you choose. If you are certain that your application is one of the exceptions, It is probably best to write your own rounding routine and test it thoroughly. This is the only way to be sure that future changes to a library will not break your code. If you choose to use an existing library, you should test every case is handled the way your application requires.
    Bill

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (5)
As of 2024-03-19 10:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found