Consider the following function that implements optimised O(log n) exponentiation by squaring:
#include <cstdint> // uintmax
// log-n optimised integer power function. Computes x to the power of y
constexpr std::uintmax_t pow(std::uintmax_t x, std::uintmax_t y) {
// base cases for efficiency and guaranteed termination
switch (y) {
case 0:
return 1;
case 1:
return x;
case 2:
return x * x;
case 3:
return x * x * x;
}
// OTHERWISE:
std::uintmax_t square_root = pow(x, y / 2);
// otherwise, work out if y is a multiple of 2 or not
if (y % 2 == 0) {
return square_root * square_root;
} else {
return square_root * square_root * x;
}
}
The base cases switch could be rewritten to take advantage of deliberate fallthrough between the various cases to "aggregate" the exponentiation of x from the 0th up to the 3rd power:
// log-n optimised integer power function. Computes x to the power of y
constexpr std::uintmax_t pow(std::uintmax_t x, std::uintmax_t y) {
// base cases for efficiency and guaranteed termination
std::uintmax_t result = 1;
switch (y) {
case 3:
result *= x;
[[fallthrough]];
case 2:
result *= x;
[[fallthrough]];
case 1:
result *= x;
[[fallthrough]];
case 0:
return result;
}
// OTHERWISE:
std::uintmax_t square_root = pow(x, y / 2);
// otherwise, work out if y is a multiple of 2 or not
if (y % 2 == 0) {
return square_root * square_root;
} else {
return square_root * square_root * x;
}
}
Now, I am aware that the general consensus is that such use of fallthrough in a switch-case is seen as a cardinal sin. I am also aware that the function would work if base cases were provided for just the zeroth and first power (or even just for the zeroth!). The reason I provide base cases up to the third is that it feels just a bit of a waste of a function call to recur into to calculate such simple powers, although perhaps to the third power is a bit overkill..?
So my question is primarily:
- Is such use of deliberate fallthrough justifiable here, or could it be justifiable for a similarly-structured example that's a bit more elaborate than chaining multiplication but which still has the commutative chaining as a property?
Secondarily:
- What do you think about the number of base cases provided here? Is this astute use of deliberately avoiding recursive calls for such simple cases, or is it premature optimisation?