Simplify math.hpp's count_leading_zeros

The old count_ones had undefined behavior in the case of arithmetic underflow,
so replace count_leading_zeros with a simpler implementation. A couple of extra
tests are added, for both signed and unsigned numbers.

Note: `std::numeric_limits<signed char>::digits` excludes the sign bit from the
size - so the `for` loop gets an off-by-one error unless 'std::make_unsigned_t`
is used somewhere.
This commit is contained in:
Steve Cotton 2022-03-30 08:03:06 +02:00 committed by Steve Cotton
parent c52c784522
commit f1cdcd8367
2 changed files with 17 additions and 143 deletions

View file

@ -29,18 +29,6 @@ static_assert(bit_width(static_cast<uint16_t>(0)) == 16);
static_assert(bit_width(static_cast<uint32_t>(0)) == 32);
static_assert(bit_width(static_cast<uint64_t>(0)) == 64);
static_assert(count_ones(0) == 0);
static_assert(count_ones(1) == 1);
static_assert(count_ones(2) == 1);
static_assert(count_ones(3) == 2);
static_assert(count_ones(4) == 1);
static_assert(count_ones(5) == 2);
static_assert(count_ones(6) == 2);
static_assert(count_ones(7) == 3);
static_assert(count_ones(8) == 1);
static_assert(count_ones(9) == 2);
static_assert(count_ones(12345) == 6);
static_assert(count_leading_zeros(static_cast<uint8_t>(1)) == 7);
static_assert(count_leading_zeros(static_cast<uint16_t>(1)) == 15);
static_assert(count_leading_zeros(static_cast<uint32_t>(1)) == 31);
@ -49,11 +37,16 @@ static_assert(count_leading_zeros(static_cast<uint8_t>(0xFF)) == 0);
static_assert(count_leading_zeros(static_cast<unsigned int>(0)) == bit_width<unsigned int>());
static_assert(count_leading_zeros(static_cast<unsigned long int>(0)) == bit_width<unsigned long int>());
static_assert(count_leading_zeros(static_cast<unsigned long long int>(0)) == bit_width<unsigned long long int>());
static_assert(count_leading_zeros(static_cast<uint16_t>(12345)) == 2); // 12345 == 0x3039
static_assert(count_leading_zeros(static_cast<int16_t>(12345)) == 2); // 12345 == 0x3039
static_assert(count_leading_zeros(uint8_t{0xff}) == 0);
static_assert(count_leading_zeros('\0') == bit_width<char>());
static_assert(count_leading_zeros('\b') == bit_width<char>() - 4);
static_assert(count_leading_zeros('\033') == bit_width<char>() - 5);
static_assert(count_leading_zeros(' ') == bit_width<char>() - 6);
static_assert(count_leading_ones(0) == 0);
static_assert(count_leading_ones(1) == 0);
static_assert(count_leading_ones(0u) == 0);
static_assert(count_leading_ones(1u) == 0);
static_assert(count_leading_ones(static_cast<uint8_t>(0xFF)) == 8);

View file

@ -122,112 +122,6 @@ constexpr std::size_t bit_width(const T&) {
return sizeof(T) * std::numeric_limits<unsigned char>::digits;
}
/**
* Returns the quantity of `1` bits in `n` i.e., `n`s population count.
*
* Algorithm adapted from:
* <https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan>
*
* This algorithm was chosen for relative simplicity, not for speed.
*
* @tparam N The type of `n`. This should be a fundamental integer type no
* greater than `UINT_MAX` bits in width; if it is not, the return value is
* undefined.
*
* @param n An integer upon which to operate.
*
* @returns the quantity of `1` bits in `n`, if `N` is a fundamental integer
* type.
*/
template<typename N>
constexpr unsigned int count_ones(N n) {
unsigned int r = 0;
while (n) {
n &= n-1;
++r;
}
return r;
}
// Support functions for `count_leading_zeros`.
#if defined(__GNUC__) || defined(__clang__)
constexpr unsigned int count_leading_zeros_impl(
unsigned char n, std::size_t w) {
// Returns the result of the compiler built-in function, adjusted for
// the difference between the width, in bits, of the built-in
// functions parameters type (which is `unsigned int`, at the
// smallest) and the width, in bits, of the input to this function, as
// specified at the call-site in `count_leading_zeros`.
return static_cast<unsigned int>(__builtin_clz(n))
- static_cast<unsigned int>(
bit_width<unsigned int>() - w);
}
constexpr unsigned int count_leading_zeros_impl(
unsigned short int n, std::size_t w) {
return static_cast<unsigned int>(__builtin_clz(n))
- static_cast<unsigned int>(
bit_width<unsigned int>() - w);
}
constexpr unsigned int count_leading_zeros_impl(
unsigned int n, std::size_t w) {
return static_cast<unsigned int>(__builtin_clz(n))
- static_cast<unsigned int>(
bit_width<unsigned int>() - w);
}
constexpr unsigned int count_leading_zeros_impl(
unsigned long int n, std::size_t w) {
return static_cast<unsigned int>(__builtin_clzl(n))
- static_cast<unsigned int>(
bit_width<unsigned long int>() - w);
}
constexpr unsigned int count_leading_zeros_impl(
unsigned long long int n, std::size_t w) {
return static_cast<unsigned int>(__builtin_clzll(n))
- static_cast<unsigned int>(
bit_width<unsigned long long int>() - w);
}
constexpr unsigned int count_leading_zeros_impl(
char n, std::size_t w) {
return count_leading_zeros_impl(
static_cast<unsigned char>(n), w);
}
constexpr unsigned int count_leading_zeros_impl(
signed char n, std::size_t w) {
return count_leading_zeros_impl(
static_cast<unsigned char>(n), w);
}
constexpr unsigned int count_leading_zeros_impl(
signed short int n, std::size_t w) {
return count_leading_zeros_impl(
static_cast<unsigned short int>(n), w);
}
constexpr unsigned int count_leading_zeros_impl(
signed int n, std::size_t w) {
return count_leading_zeros_impl(
static_cast<unsigned int>(n), w);
}
constexpr unsigned int count_leading_zeros_impl(
signed long int n, std::size_t w) {
return count_leading_zeros_impl(
static_cast<unsigned long int>(n), w);
}
constexpr unsigned int count_leading_zeros_impl(
signed long long int n, std::size_t w) {
return count_leading_zeros_impl(
static_cast<unsigned long long int>(n), w);
}
#else
template<typename N>
constexpr unsigned int count_leading_zeros_impl(N n, std::size_t w) {
// Algorithm adapted from:
// <http://aggregate.org/MAGIC/#Leading%20Zero%20Count>
for (unsigned int shift = 1; shift < w; shift *= 2) {
n |= (n >> shift);
}
return static_cast<unsigned int>(w) - count_ones(n);
}
#endif
/**
* Returns the quantity of leading `0` bits in `n` i.e., the quantity of
* bits in `n`, minus the 1-based bit index of the most significant `1` bit in
@ -249,32 +143,19 @@ constexpr unsigned int count_leading_zeros_impl(N n, std::size_t w) {
* @see count_leading_ones()
*/
template<typename N>
constexpr unsigned int count_leading_zeros(N n) {
#if defined(__GNUC__) || defined(__clang__)
// GCCs `__builtin_clz` returns an undefined value when called with 0
// as argument.
// [<http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html>]
if (n == 0) {
// Return the quantity of zero bits in `n` rather than
// returning that undefined value.
return static_cast<unsigned int>(bit_width(n));
constexpr std::enable_if_t<std::is_integral_v<N>, unsigned int> count_leading_zeros(N n) {
const auto x = static_cast<std::make_unsigned_t<N>>(n);
constexpr decltype(x) mask{1};
unsigned int count{0};
for(int i = std::numeric_limits<decltype(mask)>::digits - 1; i >= 0; --i) {
if(x & (mask << i)) {
break;
}
++count;
}
#endif
// Dispatch, on the static type of `n`, to one of the
// `count_leading_zeros_impl` functions.
return count_leading_zeros_impl(n, bit_width(n));
// The second argument to `count_leading_zeros_impl` specifies the
// width, in bits, of `n`.
//
// This is necessary because `n` may be widened (or, alas, shrunk),
// and thus the information of `n`s true width may be lost.
//
// At least, this *was* necessary before there were so many overloads
// of `count_leading_zeros_impl`, but Ive kept it anyway as an extra
// precautionary measure, that will (I hope) be optimized out.
//
// To be clear, `n` would only be shrunk in cases noted above as
// having an undefined result.
return count;
}
/**