I'm writing a wrapper for the bcmath
extension, and bug #10116 regarding bcpow()
is particularly annoying -- it casts the $right_operand
($exp
) to an (native PHP, not arbitrary length) integer, so when you try to calculate the square root (or any other root higher than 1
) of a number you always end up with 1
instead of the correct result.
I started searching for algorithms that would allow me to calculate the nth root of a number and I found this answer which looks pretty solid, I actually expanded the formula using WolframAlpha and I was able to improve it's speed by about 5% while keeping the accuracy of the results.
Here is a pure PHP implementation mimicking my BCMath implementation and its limitations:
function _pow($n, $exp)
{
$result = pow($n, intval($exp)); // bcmath casts $exp to (int)
if (fmod($exp, 1) > 0) // does $exp have a fracional part higher than 0?
{
$exp = 1 / fmod($exp, 1); // convert the modulo into a root (2.5 -> 1 / 0.5 = 2)
$x = 1;
$y = (($n * _pow($x, 1 - $exp)) / $exp) - ($x / $exp) + $x;
do
{
$x = $y;
$y = (($n * _pow($x, 1 - $exp)) / $exp) - ($x / $exp) + $x;
} while ($x > $y);
return $result * $x; // 4^2.5 = 4^2 * 4^0.5 = 16 * 2 = 32
}
return $result;
}
The above seems to work great except when 1 / fmod($exp, 1)
doesn't yield an integer. For example, if $exp
is 0.123456
, its inverse will be 8.10005
and the outcome of pow()
and _pow()
will be a bit different (demo):
pow(2, 0.123456)
=1.0893412745953
_pow(2, 0.123456)
=1.0905077326653
_pow(2, 1 / 8)
=_pow(2, 0.125)
=1.0905077326653
How can I achieve the same level of accuracy using "manual" exponential calculations?