 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 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) = \$_;
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;

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";
}

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?