How to make a C recursive exponentiation (power) function better?
I developed a pretty simple (yet decent, imo) power function for the sake of learning. I'm not quite trying to compete with math.h
's double pow(double __x, double __y)
double pwr(double _base, double _exp)
{
if (_exp == 0)
return 1;
else if (_exp > 0)
return _base * pwr(_base, _exp - 1);
else
return 1 / (_base * pwr(_base, (_exp * -1) - 1));
}
But i would like to know why (if) math.h
's would be better.
Some community guy also told me it could be further simplified, but didn't tell me how. Is that true?
Note: I'm aware of "divide and conquer" approach, but not sure how to proceed in case _exp
is not integer
3 Answers 3
For large exponents, this code is slower than necessary (it scales linearly in exp
, rather than logarithmically as an efficient implementation would). Research Binary Exponentiation algorithm, and Exponentiation by logarithm, which is what your standard library implementation is most likely to use for pow(double, double)
.
The latter relies on the identity \$\ln{(y^x)} ≡ x \ln{(y)}\$. So \$y^x = \exp(x \ln(y))\$.
The implementation here will never return when exp
is not an integer - you'll just alternate between calls either side of zero, until you run out of stack. We need to use the logarithm approach for non-integer powers (and arrange to fail when the base is negative and the power is fractional).
Avoid using reserved identifiers in your own code. This includes names that begin with underscore (_
) followed by a lower-case letter. (Not as dangerous as names beginning with _
and an upper-case letter, which are available to the implementation for use as macros, but still not good; as a general rule, never declare your own names beginning with _
and you'll be fine).
-
1\$\begingroup\$ afaik, reserved identifiers, in C follow this rule:
According to the C Standard, 7.1. 3 [ISO/IEC 9899:2011], All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
since my identifiers start with single_
and lowercase, i should be fine, no? \$\endgroup\$Gustavo Duarte– Gustavo Duarte2021年07月05日 13:48:54 +00:00Commented Jul 5, 2021 at 13:48 -
1\$\begingroup\$ Yes. Underscore and lowercase letter only reserved for use as identifiers with file scope (the following sentence). Still worth avoiding in your own code. \$\endgroup\$Toby Speight– Toby Speight2021年07月05日 13:53:31 +00:00Commented Jul 5, 2021 at 13:53
For exp
not 0, between -1 and 1 it will make a recursive call with a negated exponent.
Furthermore x2n = (xn)2 which can be used to reduce n recursions down to log2(n) recursions.
For exp = n + q, n int, 0 < q < 1 or exp = n ± q, n int, 0 < q <= 0.5 you can use xexp = (xn).(xq). For the integer power n you can do the logarithmic recursion (double
, long
or BigInteger
). For the q fraction you need an approximation sequence. Don't ask me how.
Underscore at the beginning, is extra. Nowadays the majority seems to find this less readable (as not a ubiquitous convention), and disruptive in reading. Less is more here.
pow(double _base, double _exp)
, like this pwr()
, is a challenging function.
Also an accurate implementation is difficult as errors in manipulating _base
are heavily amplified by large _exp
.
Non-integer power
When _exp
is not a whole number value, best to use exp2(_exp * log2(_base))
and avoid log(), exp()
to improve accuracy.
Note: _base < 0
will error as, in general, the result of pow(negative_base, some_non_whole_number)
is a complex number.
else
not needed
double pwr(double _base, double _exp) {
if (_exp == 0)
return 1;
// else if (_exp > 0)
if (_exp > 0)
return _base * pwr(_base, _exp - 1);
// else
return 1 / (_base * pwr(_base, (_exp * -1) - 1));
Linear recursion depth
Should _exp
be a large integer value, code will recurse many times.
Instead, halve the exponent each each recursion for a log recursion depth.
// return _base * pwr(_base, _exp - 1);
double p2 = pwr(_base, _exp/2);
return p2*p2*(_exp/2 ? _base : 1);
This also improves correctness as fewer calculations are done.
Somewhat improved code
double pwr_rev2(double base, double expo) {
// If non-integer expo, use exp2(),log2()
double expo_ipart;
double fpart = modf(expo, &expo_ipart);
if (fpart) {
return exp2(expo * log2(base));
}
if (expo_ipart < 0.0) {
// Form 1/ result
return 1.0/pwr_rev2(base, -expo);
}
if (expo_ipart == 0.0) {
return 1.0;
}
double p = pwr_rev2(base, expo_ipart / 2.0);
p *= p;
if (fmod((expo_ipart, 2)) {
p *= base;
}
return p;
}
Implementation still has short comings with 1.0/pwr_rev2(base, -expo)
as the quotient becomes 0.0 when the result should be very small, yet more than zero.
It also recurses too many times when the result p *= p;
is infinity. TBD code to fix.