Rounding in PHP

Associate
Joined
18 Oct 2002
Posts
231
Whats the deal with PHP rounding?

I've tried 4.3.2 on a win2k IIS box and 4.4.2 on a win2k apache box, and seem to have strange results using round($x , 2):

27.115 goes to 27.12
27.125 goes to 27.13
125.245 goes to 125.25
125.244 goes to 125.24
37.535 goes to 37.53 <----- Errrr why?

It's not using bankers rounding, 5 is being treated as go up, but that last one is being odd.....bug?

Different version of PHP?
 
To do with the way Computers store number. I haven't got the details to hand.. but the short version is "It sucks. Such is life."

(53.535 may be rounded up from 53.5349, so it is closer to 53.53 than it is 53.54)
 
I was thinking it might be a precision problem, my issue is that I need it to match up with the calculations done by Visual Basic, which doesn't seem to have a precision problem with it's currency datatype. Hmm
 
Code:
<?php

function safe_round ($val, $precision)
{
    $val = floatval(strval($val));
    return round ($val, $precision);
}

?>

Try that.
 
Balls.

Hmm.. I've seen this elsewhere, but my maths sucks, and it looks bit.. odd:
Code:
<?php

        function safe_round($num, $round, $precision = 4)
        {
                $x = round($num, $round + $precision);
                return round($x, $round);
        }

?>
 
There appears to be a solution on the man page for round(), amazingly from a gerbil, and Hitler's pet gerbil at that:
In response to boonedocks at gmail dot com

Your calculations are slightly incorrect. You only use the formula when the 1/1000 position is equal to 5, otherwise you follow standard rounding proceedures.

I wrote a quick snippet which works with the input as a string, uses inline substrings and substitutions. Works VERY fast.

Code:
<?php
function bankers_round ($moneyfloat = null)
{
   $money_str = sprintf("%01.3f", round($moneyfloat, 3)); // convert to rounded (to the nearest thousandth) string
   $thous_pos = strlen($money_str)-1;                    // Thousandth string position
   $hundt_pos = strlen($money_str)-2;                    // Hundredth string position
   if ($money_str[$thous_pos] === "5") $money_str[$thous_pos] = ((int)$money_str[$hundt_pos] & 1) ? "9" : "0"; // use a bitwise test, its faster than modulus
   // The above statement assists round() by setting the thousandth position to 9 or zero if the hundredth is even or odd (respectively)

   return round($money_str, 2); // Return rounded value
}

?>

Hitlers Pet Gerbil
I've not actually looked at it properly to see if's any good.
 
That's doing it to bankers rounding which I don't want but yes the principle seems to work. I've tweaked it so if the 1/1000 position is 5 then change it to a 9 and then round, else just round. Playing with it now and so far so good......
 
With a bit of quick tinkering i've got this working, though it needs a tidy up and is probably quite slow.
Code:
function super_round($moneyfloat = null)
{
	$a = strpos($moneyfloat, ".");
	if ($a > 0) {
		$a = $a + 3;
		if (substr($moneyfloat,$a,1) == "5") {
			$moneyfloat = sprintf("%01.3f", $moneyfloat);
			$moneyfloat[$a] = "9";
		}
	}
	return round($moneyfloat,2);
}

37.535 = 37.54
37.533 = 37.53
37.53499999999 = 37.53
 
Back
Top Bottom