http://www.perlmonks.org?node_id=452025

in reply to How do I round a number?

I was looking for a ceil(x,y) function similar to what exists in excel, where x is the thing to round, and y is "significance" according to Excel v9 (Office 2000), I prefer to think of it as "interval".

But, I was also intrigued by several of the other answers given (found via supersearch), and then in a fit of playing around, I created several variations below.

My personal restrictions were to use math operations, and not rely on other modules. This eliminated the printf and POSIX answers.

```use strict;
use warnings;

########
# This version takes two arguments
#    The number to round
#    And the number of places to the right or left of the decimal poin
+t
#       Positive numbers to the left, negative numbers to the right.
#       Think powers of 10.
#
# Parts of this were stolen from nodeid=8781, and nodeid=1873
# most notably from Roy Johnson and wrvhage
########
sub round {
my (\$number, \$places) = @_;
my \$sign = (\$number < 0) ? '-' : '';
my \$abs = abs(\$number);

if(\$places < 0) {
\$places *= -1;
return \$sign . substr(\$abs+("0." . "0" x \$places . "5"),
0, \$places+length(int(\$abs))+1);
} else {
my \$p10 = 10**\$places;
return \$sign . int(\$abs/\$p10 + 0.5)*\$p10;
}
}

########
# Simple Ceiling function
########
sub ceil {
my (\$num) = @_;
return int(\$num) + (\$num > int(\$num));
}

########
# Function modeled after Excel's two argument function
#    Number to act on
#    Interval to return (2 would return only multiples of 2, 3 multipl
+es of 3 etc)
########
sub ceil_xl {
my (\$num,\$interval) = @_;

return ceil(\$num / \$interval) * \$interval;
}

########
# Function derived from node_id=270920
#    Returns next nearest mulitple of 5 up to 50, then nearest 25 up t
+o 100,
#    then nearest quarter of current power of 10.
########
sub ceil_qtrs {
my (\$num) = @_;

my \$abs=int(abs(\$num));
my \$interval;

#  This next line was the originally given answer from Abigail-II,
#  it was obtuse enough that I needed to break it down to fully unders
+tand it,
#  then I wanted to modify it, and I then left my version in the more
+readable
#  style.
#      my \$frac = \$num < 100 ? 5 : (1 . ("0" x (length (\$num) - 1))) /
+ 4;

if(\$abs < 40) {
\$interval = 5;
} elsif(\$abs < 100) {
\$interval = 25;
} else {
\$interval = "1".("0"x(length(\$abs)-1));
\$interval = \$interval/4;
}

return ceil(\$num / \$interval) * \$interval;
}

my @data = qw(1 2 3.14159 4.634 5 5.165 6 9 10 10.257 13 23 89 99 100
+101 214 702 1328
-1 -2 -3.14159 -4.634 -5 -5.165 -6 -9 -10 -10.257 -13 -23 -89 -99 -
+100 -101 -214 -704 -1328
);

my \$format = "  "."%13.13s "x8 . "\n";
printf (\$format, "number","ceil_qtrs","ceil_xl(x,3)","ceil_xl(x,8)","c
+eil(x)","round(x,0)","round(x,1)","round(x,-2)");

foreach (@data) {
printf ("%13.13s ",\$_);
printf ("%13d ",ceil_qtrs(\$_));
printf ("%13d ",ceil_xl(\$_,3));
printf ("%13d ",ceil_xl(\$_,8));
printf ("%13d ",ceil(\$_));
printf ("%13d ",round(\$_,0));
printf ("%13d ",round(\$_,1));
printf ("%13.3f ",round(\$_,-2));
print "\n";
}
[download]```

Replies are listed 'Best First'.
Re: Answer: How do I round a number?
by Roy Johnson (Monsignor) on Apr 27, 2005 at 17:56 UTC
See also ikegami's scratchpad (look for "Rounding"). Make sure your solutions work under all the same conditions.

Caution: Contents may have been coded under pressure.
Wow, that's quite a scratchpad, thanks.

So, I hadn't thought much about negative numbers. Is it mathematically correct for the ceiling function to go to the next more negative number (to the left on a number line) or to truncate (ie int) a negative number (move toward the right on a number line)?

I think the rounding functions for negative numbers are correct (don't need adjusting), you're simply moving toward the nearest whole number...

-Scott

Update: As further discussed below, the code did originally have issues, the code in the answer above has now been replaced by working code.

ceil should always round toward the right on a number line, so your ceil is correct. In fact, it's the same thing I came up with in Re^2: Perl oddities. Note merlyn's response, though.

Caution: Contents may have been coded under pressure.