Skip to content

Commit

Permalink
Implement a large amount of scaffolding for handling long double alon…
Browse files Browse the repository at this point in the history
…g with slight refactor of bit_cast
  • Loading branch information
Rinzii committed Apr 5, 2024
1 parent 783840e commit 960ac95
Show file tree
Hide file tree
Showing 25 changed files with 2,450 additions and 106 deletions.
11 changes: 9 additions & 2 deletions ccmath_headers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ set(ccmath_internal_support_headers
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/is_constant_evaluated.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/meta_compare.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/unreachable.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/ctz.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/type_identity.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/floating_point_bits.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/math_support.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/always_false.hpp
)

set(ccmath_internal_types_headers
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/fp_types.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/int128.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/uint128.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/uint.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/sign.hpp
)


Expand All @@ -51,7 +58,7 @@ set(ccmath_internal_headers


##########################################
# Detail headers
# Math headers
##########################################

set(ccmath_math_basic_impl_headers
Expand Down
15 changes: 15 additions & 0 deletions include/ccmath/internal/support/always_false.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2024-Present Ian Pike
* Copyright (c) 2024-Present ccmath contributors
*
* This library is provided under the MIT License.
* See LICENSE for more information.
*/

#pragma once

namespace ccm::support
{
template <typename...>
inline constexpr bool always_false = false;
} // namespace ccm::support
198 changes: 176 additions & 22 deletions include/ccmath/internal/support/bits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
* See LICENSE for more information.
*/

// Support header that brings C++20's <bit> header to C++17.

#pragma once

#include "ccmath/internal/support/ctz.hpp"

#include <cstdint>
#include <type_traits>

namespace ccm::helpers::traits
namespace ccm::support::traits
{
// TODO: Later add this into its own header.
// clang-format off
template <typename T> struct is_char : std::false_type {};
template <> struct is_char<char> : std::true_type {};
Expand All @@ -25,11 +30,22 @@ namespace ccm::helpers::traits
template <> struct is_char<signed char> : std::true_type {};
template <> struct is_char<unsigned char> : std::true_type {};
template <typename T> inline constexpr bool is_char_v = is_char<T>::value;

template <typename T> struct is_unsigned_integer : std::false_type {};
template <> struct is_unsigned_integer<unsigned char> : std::true_type {};
template <> struct is_unsigned_integer<unsigned short> : std::true_type {};
template <> struct is_unsigned_integer<unsigned int> : std::true_type {};
template <> struct is_unsigned_integer<unsigned long> : std::true_type {};
template <> struct is_unsigned_integer<unsigned long long> : std::true_type {};
#if defined(__SIZEOF_INT128__)
template <> struct is_unsigned_integer<__uint128_t> : std::true_type {};
#endif
template <typename T> inline constexpr bool is_unsigned_integer_v = is_unsigned_integer<T>::value;
// clang-format on

} // namespace ccm::helpers::traits
} // namespace ccm::support::traits

namespace ccm::helpers
namespace ccm::support
{

/**
Expand All @@ -49,16 +65,13 @@ namespace ccm::helpers
return __builtin_bit_cast(To, src);
}

template <class T, std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T> && !ccm::helpers::traits::is_char_v<T> && !std::is_same_v<T, bool>,
bool> = true>
template <class T,
std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T> && !ccm::support::traits::is_char_v<T> && !std::is_same_v<T, bool>, bool> = true>
constexpr bool has_single_bit(T x) noexcept
{
return x && !(x & (x - 1));
}




/**
* @brief Helper function to get the top 16-bits of a double.
* @param x Double to get the bits from.
Expand All @@ -75,10 +88,9 @@ namespace ccm::helpers
}

inline constexpr std::uint32_t top12_bits_of_float(float x) noexcept
{
return bit_cast<std::uint32_t>(x) >> 20;
}

{
return bit_cast<std::uint32_t>(x) >> 20;
}

inline constexpr std::uint64_t double_to_uint64(double x) noexcept
{
Expand All @@ -96,29 +108,171 @@ namespace ccm::helpers
}

inline constexpr double int64_to_double(std::int64_t x) noexcept
{
return bit_cast<double>(x);
}
{
return bit_cast<double>(x);
}

inline constexpr std::uint32_t float_to_uint32(float x) noexcept
{
return bit_cast<std::uint32_t>(x);
}

inline constexpr std::int32_t float_to_int32(float x) noexcept
{
return bit_cast<std::int32_t>(x);
}
{
return bit_cast<std::int32_t>(x);
}

inline constexpr float uint32_to_float(std::uint32_t x) noexcept
{
return bit_cast<float>(x);
}

inline constexpr float int32_to_float(std::int32_t x) noexcept
{
return bit_cast<float>(x);
}
{
return bit_cast<float>(x);
}

/**
* @brief Rotates unsigned integer bits to the right.
* https://en.cppreference.com/w/cpp/numeric/rotr
*/
template <class T>
constexpr T rotr(T t, int cnt) noexcept
{
static_assert(ccm::support::traits::is_unsigned_integer_v<T>, "rotr requires an unsigned integer type");
const unsigned int dig = std::numeric_limits<T>::digits;
if ((static_cast<unsigned int>(cnt) % dig) == 0) { return t; }

if (cnt < 0)
{
cnt *= -1;
return (t << (static_cast<unsigned int>(cnt) % dig)) |
(t >> (dig - (static_cast<unsigned int>(cnt) % dig))); // rotr with negative cnt is similar to rotl
}

return (t >> (static_cast<unsigned int>(cnt) % dig)) | (t << (dig - (static_cast<unsigned int>(cnt) % dig)));
}

/**
* @brief Rotates unsigned integer bits to the left.
* https://en.cppreference.com/w/cpp/numeric/rotl
*/
template <class T>
constexpr T rotl(T t, int cnt) noexcept
{
return rotr(t, -cnt);
}

// https://en.cppreference.com/w/cpp/numeric/countr_zero
template <typename T>
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countr_zero(T value)
{
if (value == 0) { return std::numeric_limits<T>::digits; }

if constexpr (ccm::support::traits::is_unsigned_integer_v<T>) { return ccm::support::ctz<T>(value); }

int ret = 0;
const unsigned int ulldigits = std::numeric_limits<unsigned long long>::digits;
while (static_cast<unsigned long long>(value) == 0ULL)
{
ret += ulldigits;
value >>= ulldigits;
}
return ret + ccm::support::ctz(static_cast<unsigned long long>(value));
}

template <typename T>
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countr_one(T value)
{
return value != std::numeric_limits<T>::max() ? countr_zero(static_cast<T>(~value)) : std::numeric_limits<T>::digits;
}

template <typename T, std::enable_if_t<ccm::support::traits::is_unsigned_integer_v<T>, bool> = true>
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countl_zero(T value)
{
if (value == 0) { return std::numeric_limits<T>::digits; }

if constexpr (ccm::support::traits::is_unsigned_integer_v<T>) { return std::numeric_limits<T>::digits - ccm::support::ctz<T>(value); }

int ret = 0;
int iter = 0;
const unsigned int ulldigits = std::numeric_limits<unsigned long long>::digits;
while (true)
{
value = rotl(value, ulldigits);
if ((iter = countl_zero(static_cast<unsigned long long>(value))) != ulldigits) // NOLINT
break;
ret += iter;
}
return ret + iter;
}

template <typename T, std::enable_if_t<ccm::support::traits::is_unsigned_integer_v<T>, bool> = true>
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countl_one(T value)
{
return value != std::numeric_limits<T>::max() ? countl_zero(static_cast<T>(~value)) : std::numeric_limits<T>::digits;
}

template <typename T>
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> bit_width(T value)
{
return std::numeric_limits<T>::digits - countl_zero(value);
}

#if __has_builtin(__builtin_popcountg)
template <typename T>
[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> popcount(T value)
{
return __builtin_popcountg(value);
}
#else // !__has_builtin(__builtin_popcountg)
template <typename T>
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> popcount(T value)
{
int count = 0;
for (int i = 0; i != std::numeric_limits<T>::digits; ++i)
{
if ((value >> i) & 0x1) { ++count; }
}
return count;
}
#endif // __has_builtin(__builtin_popcountg)

// If the compiler has builtin's for popcount, the create specializations that use the builtin.
#if __has_builtin(__builtin_popcount)
template <>
[[nodiscard]] inline constexpr int popcount<unsigned char>(unsigned char value)
{
return __builtin_popcount(value);
}

template <>
[[nodiscard]] inline constexpr int popcount<unsigned short>(unsigned short value)
{
return __builtin_popcount(value);
}

template <>
[[nodiscard]] inline constexpr int popcount<unsigned>(unsigned value)
{
return __builtin_popcount(value);
}
#endif // __has_builtin(__builtin_popcount)

#if __has_builtin(__builtin_popcountl)
template <>
[[nodiscard]] inline constexpr int popcount<unsigned long>(unsigned long value)
{
return __builtin_popcountl(value);
}
#endif // __has_builtin(__builtin_popcountl)

#if __has_builtin(__builtin_popcountll)
template <>
[[nodiscard]] inline constexpr int popcount<unsigned long long>(unsigned long long value)
{
return __builtin_popcountll(value);
}
#endif // __has_builtin(__builtin_popcountll)

} // namespace ccm::helpers
} // namespace ccm::support
90 changes: 90 additions & 0 deletions include/ccmath/internal/support/ctz.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2024-Present Ian Pike
* Copyright (c) 2024-Present ccmath contributors
*
* This library is provided under the MIT License.
* See LICENSE for more information.
*/

#pragma once

#include <limits>
#include <type_traits>

namespace ccm::support
{
namespace internal
{
// Software implementation of ctz for unsigned integral types in the event that the compiler does not provide a builtin
// Mostly added for msvc support, as gcc and clang have builtins for this.
template <typename T, std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T> && !std::is_same_v<T, bool>, bool> = true>
constexpr int generic_ctz(T x) noexcept
{
// If x is 0, the result is undefined.
if (x == 0)
{
// We return the size of the type in bits to indicate undefined behavior.
// This mimics the behavior of __builtin_ctz.
return sizeof(T) * std::numeric_limits<unsigned char>::digits;
}

int count = 0;

// Loop until the least significant bit is 1
while ((x & 1) == 0)
{
++count;
x >>= 1; // Right shift x by 1 bit
}

return count;
}
} // namespace internal

template <typename T>
constexpr int ctz(T /* x */) noexcept
{
static_assert(false, "Unsupported type for ctz"); // Prevent unsupported types from compiling, but give useful error.
return -1;
}

template <>
constexpr int ctz(unsigned short x) noexcept
{
#if __has_builtin(__builtin_ctzs)
return __builtin_ctzs(x);
#else
return internal::generic_ctz(x);
#endif
}

template <>
constexpr int ctz(unsigned int x) noexcept
{
#if __has_builtin(__builtin_ctz)
return __builtin_ctz(x);
#else
return internal::generic_ctz(x);
#endif
}

template <>
constexpr int ctz(unsigned long x) noexcept
{
#if __has_builtin(__builtin_ctzl)
return __builtin_ctzl(x);
#else
return internal::generic_ctz(x);
#endif
}

template <>
constexpr int ctz(unsigned long long x) noexcept
{
#if __has_builtin(__builtin_ctzll)
return __builtin_ctzll(x);
#else
return internal::generic_ctz(x);
#endif
}
} // namespace ccm::support
Loading

0 comments on commit 960ac95

Please sign in to comment.