Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Rounding values upwards on an arbitrary basis

by ibanix (Hermit)
on Jul 02, 2003 at 19:51 UTC ( #270920=perlquestion: print w/replies, xml ) Need Help??

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

Hi monks; thanks for your help with my date formatting question yesterday.

Today I find myself needing to round integer values upwards, based on a sliding scale. It looks something like this:

# Less than 10 1 - 5: 5 6 - 9: 10 # 10 to 100 10 - 14: 15 15 - 19: 20 20 - 24: 25 etc. # 100 to 1000 100 - 124: 125 125 - 149: 150 150 - 174: 175 etc. # 1000 and beyond 1000 - 1249: 1250 1250 - 1549: 1500 1500 - 1749: 1750 etc.
As you can tell, the pattern isn't too hard to deduce. I believe I can use log10() to figure out which category I'm in, but what methods do I use to perform rounding operations based on a ruleset like this? Is there something like ceiling() with the ability to define my boundries?

Thanks all!
ibanix

$ echo '$0 & $0 &' > foo; chmod a+x foo; foo;

Replies are listed 'Best First'.
Re: Rounding values upwards on an arbitrary basis
by Abigail-II (Bishop) on Jul 02, 2003 at 20:54 UTC
    #!/usr/bin/perl use strict; use warnings; use POSIX qw /ceil/; my @data = qw /1 2 4 5 9 10 99 100 110 124 125 900 999 1000 1100/; foreach (@data) { my $frac = $_ < 100 ? 5 : (1 . ("0" x (length ($_) - 1))) / 4; my $new = $frac * ceil ($_ / $frac); print "$_: $new\n"; } __END__ 1: 5 2: 5 4: 5 5: 5 9: 10 10: 10 99: 100 100: 100 110: 125 124: 125 125: 125 900: 900 999: 1000 1000: 1000 1100: 1250

    Abigail

      Well, the bizarre thing about this problem is that the function ibanix described is not idempotent. E.g. f(124)=125 but f(f(124))=150. So it shouldn't really be called a "rounding" function. I don't know if that's really what he wanted, but it is what he asked for.

      Abigail's function is more reasonable, but not what ibanix asked for.

        Yeah, well you could solve that by adding something like:
        $new += $frac if int ($_ / $frac) == ceil ($_ / $frac);

        Abigail

Re: Rounding values upwards on an arbitrary basis
by Thelonius (Priest) on Jul 02, 2003 at 20:56 UTC
    I would do it with a binary search:
    #!perl -w use strict; my %round; my @lowend; my $prevend = 0; # depends on input being sorted while (<DATA>) { if (/(\d+)\s*-\s*(\d+)\s*:\s*(\d+)/) { my ($start, $end, $target) = ($1, $2, $3); if ($start != $prevend + 1) { print STDERR "Warning: prevend=$prevend input=$_" } $prevend=$end; push @lowend, $start; $round{$start} = $target; } } sub customround { my ($val) = $_[0]; my $low = 0; my $high = $#lowend; # binary search while ($low < $high) { my $mid = int(($low + $high)/2); if ($lowend[$mid] < $val) { $low = $mid + 1; } elsif ($lowend[$mid] > $val) { $high = $mid - 1; } else { # found exact return $round{$lowend[$mid]}; } } $low++ unless $low == $#lowend; while ($low > 0 && $lowend[$low] > $val) { $low-- } return $round{$lowend[$low]}; } # test print "customround($_) = ", customround($_), "\n" for qw(0 1 5 6 7 10 14 15 16 19 20 99 100 124 125 126 1499 1500 1501) +; __DATA__ # Less than 10 1 - 5: 5 6 - 9: 10 # 10 to 100 10 - 14: 15 15 - 19: 20 20 - 24: 25 etc. # 100 to 1000 100 - 124: 125 125 - 149: 150 150 - 174: 175 etc. # 1000 and beyond 1000 - 1249: 1250 1250 - 1549: 1500 1500 - 1749: 1750 etc.
Re: Rounding values upwards on an arbitrary basis
by BrowserUk (Patriarch) on Jul 02, 2003 at 21:35 UTC

    My contribution to the fray.

    Update:Removed spurious hash left over from an earlier attempt.

    #! perl -slw use strict; sub categorise { use POSIX qw[log10]; my $n = shift; die 'Argument less than 1' unless $n > 0; return 5 if $n <= 5; return 10 if $n <=10; return 5 * int( $n / 5 + 1 ) if $n <= 100; my $scale = int( log10( $n ) ); return (0+"1e$scale") * 0.25 * ( 1 + int( ( $n / (0+"1e$scale") ) / 0.25 ) ); } my $t = 2.4; for( 1 .. 20 ) { $t *= 1.55; printf "%5d : %5d\n", int($t), categorise( int( $t ) ); } =pod comment Expose for a more thorough and randomised test. printf "%5d : %5d\n", int($_), categorise( int( $_ ) ) for 1..15, ( map{ 10* ($_ + rand) } 2 .. 9 ), ( map{ 100* ($_ + rand) } 1 .. 9 ), ( map{ 1000* ($_ + rand) } 1 .. 9 ); =cut __END__ P:\>270920 3 : 5 5 : 5 8 : 10 13 : 15 21 : 25 33 : 35 51 : 55 79 : 80 123 : 125 192 : 200 297 : 300 461 : 475 715 : 725 1108 : 1250 1718 : 1750 2663 : 2750 4129 : 4250 6400 : 6500 9920 : 10000 15376 : 17500

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller


Re: Rounding values upwards on an arbitrary basis
by xdg (Monsignor) on Jul 02, 2003 at 21:09 UTC

    Here's an approach to the rounding up function. I've left the algorithm of what to round by at each level as exercise for the reader, as you said you know how using log()

    CODE: #!/usr/bin/perl -w use strict; # $n is number to round, $d is "divisor" for rounding units sub roundup { my ($n, $d) = @_; return ( int( $n / $d ) + ( ( $n % $d ) ? 1 : 0 ) ) * $d; } for my $i ( 0,4,5,6,9,10,11,24,25,26) { print "$i -> " . roundup($i,5) . "\n"; } for my $i ( 99,100,101,124,125,126,149,150,151) { print "$i -> " . roundup($i,25) . "\n"; } for my $i ( 999, 1000, 1001, 1249, 1250, 1251, 1499,1500,1501) { print "$i -> " . roundup($i,250) . "\n"; } RESULT: 0 -> 0 4 -> 5 5 -> 5 6 -> 10 9 -> 10 10 -> 10 11 -> 15 24 -> 25 25 -> 25 26 -> 30 99 -> 100 100 -> 100 101 -> 125 124 -> 125 125 -> 125 126 -> 150 149 -> 150 150 -> 150 151 -> 175 999 -> 1000 1000 -> 1000 1001 -> 1250 1249 -> 1250 1250 -> 1250 1251 -> 1500 1499 -> 1500 1500 -> 1500 1501 -> 1750

    -xdg

    Addendum: This rounds up, but not over -- for the case where 4 rounds up to 5, but 5 rounds up to 10 (!), then the function is even simpler:

    sub roundup { my ($n, $d) = @_; return ( int( $n / $d ) + 1 ) * $d; }

    Code posted by xdg on PerlMonks is public domain. It has no warranties, express or implied. Posted code may not have been tested. Use at your own risk.

Re: Rounding values upwards on an arbitrary basis
by Not_a_Number (Prior) on Jul 02, 2003 at 20:47 UTC
    Not quite sure what you meean by "and beyond".

    Should eg 10249 (or 10000) resolve to 10250 or 12500? Nice question, though.
Re: Rounding values upwards on an arbitrary basis
by shemp (Deacon) on Jul 02, 2003 at 21:01 UTC
    The code below should work. I've tried it on a bunch of numbers. Its disappointing that it needs the extra checks for numbers less than 100, but that rounding is degenerate compared to the rest of the algorithm. Without those checks, 12 rounds to 12.5 - which is definitely wrong
    { my $number = $ARGV[0]; my $floor_log = int( log10($number) ); my $category = 10**$floor_log; my $as_decimal = $number / $category; my $integer_part = int($as_decimal); my $decimal_part = $as_decimal - $integer_part; my $rounded = $integer_part; if ( ($number >= 100) && ($decimal_part < 0.25) ) { $rounded += .25; } elsif ( $decimal_part < 0.5 ) { $rounded += .5; } elsif ( ($number >= 100) && ($decimal_part < 0.75) ) { $rounded += .75; } else { $rounded += 1; } $rounded *= $category; print "number = $number\n"; print "rounded = $rounded\n"; }

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (3)
As of 2022-08-10 05:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?