note 35098 added to function.round
| From: | donnieb819 at rack1 dot php dot net | Date: | Tue, 19 Aug 2003 16:32:19 +0000 |
| Subject: | note 35098 added to function.round | ||
| Groups: | php.notes | ||
| Request: | Send a blank email to php-notes+get-54705@lists.php.net to get a copy of this message | ||
I needed to be able to round a number based on its fractional rather than integer part, thus to the
closest 1/2, or 1/3 and 2/3 or 1/4, 1/2 and 3/4, etc. I wrote a function to do it which may be of
some use:
// round num into an arbitrary number of sub-integer 'bins' according to the
// standard rounding rules. Thus, for n = 2, there are two bins per integer,
// values from x.0 to < x.5 and >= x.5 to (1 + x).0
// following this example, 1.1 with n = 2 rounds to 1.0, 1.2 to 1.0, 1.25 to 1.5,
// 1.5 to 1.5, 1.50001 to 1.5, 1.75 to 2.0, 1.9 to 2.0
// The idea is that you can determine when to round by looking at the slope of
// the tangent line on the abs sine wave modulated by sin(n * PI * x), where n
// is the number of 'bins'. The sine wave can be thought of as a series of hills
// where a real on either side of the hill will 'roll down' the hill, essentially
// following the slope of its tangent, towards its nearest bin value. A point that
// lands at the top of a hill (precisely in between two bin values) would
// theoretically have a slope of 0 (doesn't seem to happen in practice, though
// we can detect this situation) and needs a little 'push' in one direction or the
// other. Push it forward (round up) for odds, or and backwards (round down)
// for evens.
function bin($num,$n = 2)
{
// easier to work only with positive values, then return the negative result
if($num < 0)
{
$neg_f = true; // remember that the answer must be negative
$num = abs($num); // use the absolute value
}
$num = rtrim($num,'0'); // get rid of trailing 0's
$bin_val = 1 / $n; // the value which separates the n number of bins.
$frac = $num - (int)($num); // the fractional piece of arg
$int = (int)($num); // the integral piece of arg
// the slope of the tangent of the sine wave at num
$result = cos($n * M_PI * $num);
// pretend we have abs sine by looking at the second derivative and if
// it's negative, flip result. (This makes sense also if you graph
// both sin(PI * x) and -sin(PI * x), the hills separating the bin values
// become apparant, and the negative hills (valleys) are just ignored.
if((-sin($n * M_PI * $num) > 0) || ($result == -1)) // also, -1 is the same as 1
$result = -$result;
// if num is either an integer, or lands directly on a bin, it is rounded already
// (landed directly between two hills)
if(!fmod($frac,$bin_val))
{
$result = 1;
// num is exactly half-way between two bins (on top of a hill), so round
// down on evens and up on odds
} elseif(!fmod($frac,$bin_val / 2)) {
if(!($int % 2)) // even: round up
$result = 3; // (these '3' and '-3' don't
matter, just needed a pos and neg non-one value to tell which way to round)
else
$result = -3; // odd: round down
}
// now we know whether we are rounding, so it's straightforward to determine
// by how much we are rounding.
if($result == 1) // not rounding
{
$ret = $num;
} elseif($result < 0) { // round up
$ret = $int + (int)($frac / $bin_val) * $bin_val + $bin_val;
} elseif($result > 0) { // round down
$ret = $int + (int)($frac / $bin_val) * $bin_val;
}
// preserve the sign
if($neg_f)
$ret = -$ret;
return $ret;
}
----
Manual Page -- http://www.php.net/manual/en/function.round.php
Edit Note -- http://master.php.net/manage/user-notes.php?action=edit+35098
Delete Note -- http://master.php.net/manage/user-notes.php?action=delete+35098&report=yes
Reject Note -- http://master.php.net/manage/user-notes.php?action=reject+35098&report=yes