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?
_pow
'rounds' the fractional part to the nearest1/n
. You could make this work recursively. So after computing_pow(2, 0.125)
, you calculate_pow(2,0.125-123456)
and so on. – Thermobarometerexp
andlog
or are there other reasons whya^b = exp(b*log(a))
isn't an option? The recursion Jeffrey suggests would of course work, but its speed may not be satisfactory if you need many1/k
to represent the exponent. Is writing the exponent as a rational numbern/d
and calculating(a^n)^(1/d)
an option, or must too largen
andd
be expected? Perhaps worth an investigation is approximating the exponent by a rational number with smallish denominator (continued fraction expansion) and doing the rest with recursion. – Priapusbcmath
API is pretty poor, besides*/+-
we havesqrt
and a crippledpow
: php.net/manual/en/ref.bc.php. One problem I see with calculating(a^n)^(1/d)
is that1/d
might also be a irrational number. Either way, I asked this mostly because I was curious -- I doubt I'll need to use irrational exponents on such big numbers. =) – Thyroiditis