An unsigned integer version of pow()
is fairly easy to code, yet I also wanted:
- overflow detection
- portability
- only modest performance impact due to overflow detection.
The below codes uses a simple >=
test in the loop to see if the calculation is nearing overflow. When needed, in a 2nd loop, more expensive /
tests are used.
[ Code also uses a nifty IMAX_BITS
macro to find the number of value bits in a ..._MAX
constant as CHAR_BIT * sizeof(type)
fails when rare padding exist or the MAX
value is not the MAX
of the type, like RAND_MAX
. ]
Review goals:
Performance improvement ideas for
uintmax_t pow_umax(uintmax_t base, unsigned expo)
, especially concerning overflow detection.General review about
pow.h
andpow.c
.General review about test code functionally, not code.
pow.h
header
#ifndef POW_H
#define POW_H 1
#include <inttypes.h>
// Return base raised to the expo power.
// On overflow, set errno = ERANGE and return UINTMAX_MAX.
uintmax_t pow_umax(uintmax_t base, unsigned expo);
#endif
pow.c
code
#include "pow.h"
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
// https://stackoverflow.com/a/4589384/2410359
/* Number of value bits in inttype_MAX, or in any (1<<k)-1 where 0 <= k < 2040 */
#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))
// Value bit width of UINTMAX_MAX (handles padded types).
#define UINTMAX_BITS IMAX_BITS(UINTMAX_MAX)
// When `base >= cubert(UINTMAX_MAX + 1)` potentially the next
// `base *= base` and `pow *= base` may overflow.
// Course lower bound cube root of (UINTMAX_MAX + 1)
#define UINTMAX_MAXP1_CBRT (((uintmax_t) 1) << (UINTMAX_BITS/3))
// Detect overflow in an integer pow() calculation with minimal overhead.
uintmax_t pow_umax(uintmax_t base, unsigned expo) {
uintmax_t pow = 1;
for (;;) {
if (expo % 2) {
pow *= base;
}
expo /= 2;
if (expo == 0) {
return pow;
}
// If base is so big, it might overflow `base *= base` or `pow *= base`.
if (base >= UINTMAX_MAXP1_CBRT) {
break;
}
base *= base;
}
// `base` is large here, add precise tests for `base *= base` and `pow *= base`.
for (;;) {
if (base > UINTMAX_MAX / base) {
break;
}
base *= base;
if (expo % 2) {
if (pow > UINTMAX_MAX / base) {
break;
}
pow *= base;
}
expo /= 2;
if (expo == 0) {
return pow;
}
}
errno = ERANGE;
return UINTMAX_MAX;
}
Test code
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "pow.h"
// Bit width of various ..._MAX (handles padded types).
#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))
#define UINTMAX_BITS IMAX_BITS(UINTMAX_MAX)
#define RAND_MAX_BITS IMAX_BITS(RAND_MAX)
#define UNSIGNED_BITS IMAX_BITS(UINT_MAX)
// Random number usage on this.
_Static_assert(((RAND_MAX + 1u) & RAND_MAX) == 0, "RAND_MAX is not a Mersenne number");
// Support for failing implementations is not a review goal.
// Return random number [0...UINTMAX_MAX]
uintmax_t rand_umax(void) {
uintmax_t u = 0;
for (unsigned bits = 0; bits < UINTMAX_BITS; bits += RAND_MAX_BITS) {
u = (u << RAND_MAX_BITS) ^ (rand() & RAND_MAX);
}
return u;
}
// Return random number [0...UINT_MAX]
unsigned rand_u(void) {
unsigned u = 0;
for (unsigned bits = 0; bits < UNSIGNED_BITS; bits += RAND_MAX_BITS) {
u = (u << RAND_MAX_BITS) ^ (rand() & RAND_MAX);
}
return u;
}
_Static_assert(sizeof(long double)*CHAR_BIT >= 80, "Need wider long double for verification");
// Test pow_umax() for errno and return value correctness.
int pow_umax_test(uintmax_t base, unsigned expo) {
errno = 0;
uintmax_t p = pow_umax(base, expo);
if (errno && p != UINTMAX_MAX) {
printf("Error: %ju, %u", base, expo);
exit(EXIT_FAILURE);
}
// Use powl() as a reference function.
long double y = powl(base, expo);
if ((errno == 0 && y != p) || (errno && y <= UINTMAX_MAX)) {
printf("Error: %ju, %u --> p=%ju, y=%Lf", base, expo, p, y);
exit(EXIT_FAILURE);
}
return 0;
}
// Perform multiple pow_umax() tests.
int pow_umax_tests(void) {
printf("uintmax_t bit width: %ju\n", UINTMAX_BITS);
printf("unsigned bit width : %u\n", UNSIGNED_BITS);
printf("RAND_MAX bit width : %u\n", RAND_MAX_BITS);
printf("uintmaxp1_cbrt : 0x%jX\n", UINTMAX_MAXP1_CBRT);
int err = 0;
// The usual suspects.
uintmax_t b[] = {0, 1, 2, UINTMAX_MAXP1_CBRT - 1, UINTMAX_MAXP1_CBRT,
UINTMAX_MAXP1_CBRT + 1, UINTMAX_MAX - 1, UINTMAX_MAX};
unsigned e[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, UINT_MAX - 1, UINT_MAX};
size_t bn = sizeof b / sizeof b[0];
size_t en = sizeof e / sizeof e[0];
for (unsigned bi = 0; bi < bn; bi++) {
for (unsigned ei = 0; ei < en; ei++) {
err += pow_umax_test(b[bi], e[ei]);
}
}
// Random tests
for (unsigned long i = 0; i < 10000000u; i++) {
err += pow_umax_test(rand_umax(), rand_u());
}
return err;
}
int main(void) {
pow_umax_tests();
puts("Done");
}
Sample result (no errors)
uintmax_t bit width: 64
unsigned bit width : 32
RAND_MAX bit width : 31
uintmaxp1_cbrt : 0x200000
Done