blob: 938928d7ef851294461c7883ca73c9186057f473 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "ignite/common/detail/config.h"
#include <array>
#include <cassert>
#include <climits>
#include <cstdint>
#include <type_traits>
/**
* Maximum number of digits in std::uint64_t number.
*/
const std::int32_t UINT64_MAX_PRECISION = 20;
#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
# define IGNITE_STD_BITOPS 1
#endif
#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L
# define IGNITE_STD_INT_POW2 1
#endif
#if IGNITE_STD_BITOPS || IGNITE_STD_INT_POW2
# include <bit>
#endif
#if !IGNITE_STD_BITOPS || !IGNITE_STD_INT_POW2
# include <limits>
#endif
#if !IGNITE_STD_BITOPS && defined(_MSC_VER)
# include <intrin.h>
#endif
namespace ignite::detail {
/**
* Returns the number of consecutive 0 bits in the value of x, starting from the least significant bit ("right").
*/
template<typename T>
int countr_zero(T value) noexcept {
static_assert(std::is_unsigned_v<T>, "countr_zero() doesn't support this type");
static_assert(sizeof(T) <= sizeof(unsigned long long), "countr_zero() doesn't support this type size");
#if IGNITE_STD_BITOPS
return std::countr_zero(value);
#elif defined(__GNUC__) || defined(__clang__)
if (value == 0) {
return std::numeric_limits<T>::digits;
}
if constexpr (sizeof(T) <= sizeof(unsigned int)) {
return __builtin_ctz(value);
} else if constexpr (sizeof(T) <= sizeof(unsigned long)) {
return __builtin_ctzl(value);
} else {
return __builtin_ctzll(value);
}
#elif defined(_MSC_VER)
unsigned long index;
if constexpr (sizeof(T) <= sizeof(unsigned long)) {
return _BitScanForward(&index, value) ? index : std::numeric_limits<T>::digits;
} else {
return _BitScanForward64(&index, value) ? index : std::numeric_limits<T>::digits;
}
#else
# error "TODO: implement countr_zero() for other compilers"
#endif
}
/**
* Returns the number of consecutive 0 bits in the value of x, starting from the most significant bit ("left").
*/
template<typename T>
int countl_zero(T value) noexcept {
static_assert(std::is_unsigned_v<T>, "countl_zero() doesn't support this type");
static_assert(sizeof(T) <= sizeof(unsigned long long), "countl_zero() doesn't support this type size");
#if IGNITE_STD_BITOPS
return std::countl_zero(value);
#elif defined(__GNUC__) || defined(__clang__)
if (value == 0) {
return std::numeric_limits<T>::digits;
}
if constexpr (sizeof(T) <= sizeof(unsigned int)) {
constexpr auto extraBits = std::numeric_limits<unsigned int>::digits - std::numeric_limits<T>::digits;
return __builtin_clz(value) - extraBits;
} else if constexpr (sizeof(T) <= sizeof(unsigned long)) {
constexpr auto extraBits = std::numeric_limits<unsigned long>::digits - std::numeric_limits<T>::digits;
return __builtin_clzl(value) - extraBits;
} else {
constexpr auto extraBits = std::numeric_limits<unsigned long long>::digits - std::numeric_limits<T>::digits;
return __builtin_clzll(value) - extraBits;
}
#elif defined(_MSC_VER)
unsigned long index;
constexpr auto bit_size = std::numeric_limits<T>::digits;
if constexpr (sizeof(T) <= sizeof(unsigned long)) {
return _BitScanReverse(&index, value) ? bit_size - index - 1 : bit_size;
} else {
return _BitScanReverse64(&index, value) ? bit_size - index - 1 : bit_size;
}
#else
# error "TODO: implement countl_zero() for other compilers"
#endif
}
/**
* If x is not zero, calculates the number of bits needed to store the value x, that is, 1 + ⌊log2(x)⌋.
* If x is zero, returns zero.
*/
template<typename T>
int bit_width(T value) noexcept {
static_assert(std::is_unsigned_v<T>, "bit_width() doesn't support this type");
#if IGNITE_STD_INT_POW2
return std::bit_width(value);
#else
return std::numeric_limits<T>::digits - countl_zero(value);
#endif
}
/**
* If value is not zero, calculates the largest integral power of two that is not greater than value.
* If value is zero, returns zero.
*/
template<typename T>
T bit_floor(T value) noexcept {
static_assert(std::is_unsigned_v<T>, "bit_floor() doesn't support this type");
#if IGNITE_STD_INT_POW2
return std::bit_floor(value);
#else
return value == 0 ? 0u : T(1u) << (bit_width(value) - 1);
#endif
}
/**
* Calculates the smallest integral power of two that is not smaller than a given value.
*/
template<typename T>
T bit_ceil(T value) noexcept {
static_assert(std::is_unsigned_v<T>, "bit_ceil() doesn't support this type");
#if IGNITE_STD_INT_POW2
return std::bit_ceil(value);
#else
// TODO: If bit_width(value - 1) is equal to the number of bits in T then this should
// be UB e.g. reported by the UB sanitizer. But because of the integer promotion in
// the expression below this might be hidden. A solution is actually provided in the
// "Possible implementation" section here:
// https://en.cppreference.com/w/cpp/numeric/bit_ceil
// But do we really need this complication in our code? We can use somewhat relaxed
// rules as compared to the standard library.
return value <= 1u ? 1u : T(1u) << bit_width(T(value - 1u));
#endif
}
/**
* Get a number of bytes needed to store a specified number of bits of information.
*
* @param bits_num Number of bits to store.
* @return Required bytes number.
*/
[[nodiscard]] inline std::size_t bytes_for_bits(std::size_t bits_num) {
return (bits_num + CHAR_BIT - 1) / CHAR_BIT;
}
/**
* Get n-th power of ten.
*
* @param n Power. Should be in range [0, UINT64_MAX_PRECISION]
* @return 10 pow n, if n is in range [0, UINT64_MAX_PRECISION].
* Otherwise, behaviour is undefined.
*/
inline std::uint64_t ten_power_u64(std::int32_t n) {
static const std::array<uint64_t, UINT64_MAX_PRECISION> TEN_POWERS_TABLE{
1U, // 0 / 10^0
10U, // 1 / 10^1
100U, // 2 / 10^2
1000U, // 3 / 10^3
10000U, // 4 / 10^4
100000U, // 5 / 10^5
1000000U, // 6 / 10^6
10000000U, // 7 / 10^7
100000000U, // 8 / 10^8
1000000000U, // 9 / 10^9
10000000000U, // 10 / 10^10
100000000000U, // 11 / 10^11
1000000000000U, // 12 / 10^12
10000000000000U, // 13 / 10^13
100000000000000U, // 14 / 10^14
1000000000000000U, // 15 / 10^15
10000000000000000U, // 16 / 10^16
100000000000000000U, // 17 / 10^17
1000000000000000000U, // 18 / 10^18
10000000000000000000U // 19 / 10^19
};
assert(n >= 0 && n < UINT64_MAX_PRECISION);
return TEN_POWERS_TABLE[n];
}
/**
* Get the number of decimal digits of the integer value.
*
* @param value The value.
* @return The number of decimal digits of the integer value.
*/
[[nodiscard]] inline std::int32_t digit_length(std::uint64_t value) {
// See http://graphics.stanford.edu/~seander/bithacks.html
// for the details on the algorithm.
if (value < 10)
return 1;
std::int32_t r = ((64 - countl_zero(value) + 1) * 1233) >> 12;
assert(r <= UINT64_MAX_PRECISION);
return (r == UINT64_MAX_PRECISION || value < ten_power_u64(r)) ? r : r + 1;
}
} // namespace ignite::detail