I chose the Alix Axel's variant for rounding as it is the fastest since it only uses addition and subtraction, not multiplication and division. To round with negative precision at the beginnig I used standard function:
sprintf('%.0F', round($result, $operand_value))
But I faced the problem described here. So I extended this variant for negative precision:
function bcround($number, $precision)
{
if($precision >= 0)
{
if (strpos($number, '.') !== false)
{
if ($number[0] != '-')
return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}
return $number;
}
else
{
$mod = bcmod($number, bcpow(10, -$precision));
$sub = bcsub($number, $mod);
if($mod[0] != '-')
{
$add = $mod[0] >= 5 ? bcpow(10, strlen($mod)) : 0;
}
else
{
$add = $mod[1] >= 5 ? '-'.bcpow(10, strlen($mod)-1) : 0;
}
return bcadd($sub, $add);
}
}
A more elegant and shorter option through recursion:
function bcround($number, $precision)
{
if($precision >= 0)
{
if (strpos($number, '.') !== false)
{
if ($number[0] != '-')
return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}
return $number;
}
else
{
$pow = bcpow(10, -$precision);
return bcmul(bcround(bcdiv($number, $pow, -$precision), 0), $pow);
}
}
But it is slower because it uses two operations (division and multiplication) versus one operation of finding the remainder of the division (mod) in the first case. Speed tests have confirmed this:
First variant. Total iterations:10000. Duration: 0.24502515792847 seconds.
Second variant. Total iterations:10000. Duration: 0.35303497314453 seconds.