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:
parent
c52c784522
commit
f1cdcd8367
2 changed files with 17 additions and 143 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
// function’s parameter’s 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__)
|
||||
// GCC’s `__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 I’ve 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue