Wednesday, December 19, 2007

PHP: built-in function round() on 32-bit and 64-bit machines

My customer was complaining about a weird rounding problem recently. I do my development work primarily on a 32-bit machine, but the production server is a 64-bit machine. When I tested to run round(), a PHP built-in function, 32-bit machine and 64-bit machine produced different results. So here is my quick fix to properly round a floating number.

<?php

define
('ROUND_HALF_DOWN', 1);
define('ROUND_HALF_EVEN', 2);
define('ROUND_HALF_UP', 3);

/**
* Round floating point number because the built-in round() function produces
* different results on 32-bit and 64-bit machines
*
* @param float $value Floating poing value
* @param integer $prec Precision
* @param integer $rounding Rounding option
*/
function myround($value, $prec=2, $rounding=2) {
list(
$b, $f) = explode('.', (string) $value);
$b = (int) $b;
if ((
$prec - strlen($f)) > 0) {
$f *= pow(10, ($prec - strlen($f)));
}
if (
strlen($f) > $prec) {
$f1 = (int) substr($f, 0, $prec);
$f2 = (int) substr($f, $prec, 1);
$f3 = (int) substr($f, $prec-1, 1);
if (
$rounding === ROUND_HALF_DOWN ||
(
$rounding === ROUND_HALF_EVEN && (($f3 & 1) === 0))) {
$f = ($f2 >= 6) ? $f1 + 1 : $f1;
} elseif (
$rounding === ROUND_HALF_UP ||
(
$rounding === ROUND_HALF_EVEN && (($f3 & 1) === 1))) {
$f = ($f2 >= 5) ? $f1 + 1 : $f1;
}
if (
$f === pow(10, $prec)) {
++
$b;
$f = 0;
}
}
$f = sprintf("%0{$prec}d", $f);
return (float) ((string)
$b . '.' . (string) $f);
}

?>

Then some quick tests:
<?php
echo "1.35: " . myround(1.35, 2, ROUND_HALF_UP) . "\n";
echo "1.425: " . myround(1.90*0.75, 2, ROUND_HALF_UP) . "\n";
echo "1.425: " . myround(1.90*0.75, 3, ROUND_HALF_UP) . "\n";
echo "1.425: " . myround(1.90*0.75, 2, ROUND_HALF_EVEN) . "\n";
echo "1.995: " . myround(1.995, 2, ROUND_HALF_UP) . "\n";
echo "1.995: " . myround(1.995, 2, ROUND_HALF_EVEN) . "\n";
echo "1.995: " . myround(1.995, 2, ROUND_HALF_DOWN) . "\n";
echo "1.015: " . myround(1.015, 2, ROUND_HALF_UP) . "\n";
echo "1.015: " . myround(1.015, 2, ROUND_HALF_EVEN) . "\n";
echo "1.000: " . myround(1.00, 2, ROUND_HALF_UP) . "\n";
echo "4.20: " . myround(4.20, 2, ROUND_HALF_UP) . "\n";
?>

1 comment:

Daniel said...

Good work, thanks for sharing! We were getting rounding issues too, very strange stuff... PHP 5.3.0 apparently fixes the issue by adding a mode parameter, but we don't have that...