Skip to content

Commit 960ac95

Browse files
committed
Implement a large amount of scaffolding for handling long double along with slight refactor of bit_cast
1 parent 783840e commit 960ac95

25 files changed

+2450
-106
lines changed

ccmath_headers.cmake

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ set(ccmath_internal_support_headers
3030
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/is_constant_evaluated.hpp
3131
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/meta_compare.hpp
3232
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/unreachable.hpp
33+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/ctz.hpp
34+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/type_identity.hpp
35+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/floating_point_bits.hpp
36+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/math_support.hpp
37+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/support/always_false.hpp
3338
)
3439

3540
set(ccmath_internal_types_headers
3641
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/fp_types.hpp
37-
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/int128.hpp
42+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/uint128.hpp
43+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/uint.hpp
44+
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/types/sign.hpp
3845
)
3946

4047

@@ -51,7 +58,7 @@ set(ccmath_internal_headers
5158

5259

5360
##########################################
54-
# Detail headers
61+
# Math headers
5562
##########################################
5663

5764
set(ccmath_math_basic_impl_headers
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (c) 2024-Present Ian Pike
3+
* Copyright (c) 2024-Present ccmath contributors
4+
*
5+
* This library is provided under the MIT License.
6+
* See LICENSE for more information.
7+
*/
8+
9+
#pragma once
10+
11+
namespace ccm::support
12+
{
13+
template <typename...>
14+
inline constexpr bool always_false = false;
15+
} // namespace ccm::support

include/ccmath/internal/support/bits.hpp

Lines changed: 176 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
* See LICENSE for more information.
77
*/
88

9+
// Support header that brings C++20's <bit> header to C++17.
10+
911
#pragma once
1012

13+
#include "ccmath/internal/support/ctz.hpp"
14+
1115
#include <cstdint>
1216
#include <type_traits>
1317

14-
namespace ccm::helpers::traits
18+
namespace ccm::support::traits
1519
{
20+
// TODO: Later add this into its own header.
1621
// clang-format off
1722
template <typename T> struct is_char : std::false_type {};
1823
template <> struct is_char<char> : std::true_type {};
@@ -25,11 +30,22 @@ namespace ccm::helpers::traits
2530
template <> struct is_char<signed char> : std::true_type {};
2631
template <> struct is_char<unsigned char> : std::true_type {};
2732
template <typename T> inline constexpr bool is_char_v = is_char<T>::value;
33+
34+
template <typename T> struct is_unsigned_integer : std::false_type {};
35+
template <> struct is_unsigned_integer<unsigned char> : std::true_type {};
36+
template <> struct is_unsigned_integer<unsigned short> : std::true_type {};
37+
template <> struct is_unsigned_integer<unsigned int> : std::true_type {};
38+
template <> struct is_unsigned_integer<unsigned long> : std::true_type {};
39+
template <> struct is_unsigned_integer<unsigned long long> : std::true_type {};
40+
#if defined(__SIZEOF_INT128__)
41+
template <> struct is_unsigned_integer<__uint128_t> : std::true_type {};
42+
#endif
43+
template <typename T> inline constexpr bool is_unsigned_integer_v = is_unsigned_integer<T>::value;
2844
// clang-format on
2945

30-
} // namespace ccm::helpers::traits
46+
} // namespace ccm::support::traits
3147

32-
namespace ccm::helpers
48+
namespace ccm::support
3349
{
3450

3551
/**
@@ -49,16 +65,13 @@ namespace ccm::helpers
4965
return __builtin_bit_cast(To, src);
5066
}
5167

52-
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>,
53-
bool> = true>
68+
template <class T,
69+
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>
5470
constexpr bool has_single_bit(T x) noexcept
5571
{
5672
return x && !(x & (x - 1));
5773
}
5874

59-
60-
61-
6275
/**
6376
* @brief Helper function to get the top 16-bits of a double.
6477
* @param x Double to get the bits from.
@@ -75,10 +88,9 @@ namespace ccm::helpers
7588
}
7689

7790
inline constexpr std::uint32_t top12_bits_of_float(float x) noexcept
78-
{
79-
return bit_cast<std::uint32_t>(x) >> 20;
80-
}
81-
91+
{
92+
return bit_cast<std::uint32_t>(x) >> 20;
93+
}
8294

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

98110
inline constexpr double int64_to_double(std::int64_t x) noexcept
99-
{
100-
return bit_cast<double>(x);
101-
}
111+
{
112+
return bit_cast<double>(x);
113+
}
102114

103115
inline constexpr std::uint32_t float_to_uint32(float x) noexcept
104116
{
105117
return bit_cast<std::uint32_t>(x);
106118
}
107119

108120
inline constexpr std::int32_t float_to_int32(float x) noexcept
109-
{
110-
return bit_cast<std::int32_t>(x);
111-
}
121+
{
122+
return bit_cast<std::int32_t>(x);
123+
}
112124

113125
inline constexpr float uint32_to_float(std::uint32_t x) noexcept
114126
{
115127
return bit_cast<float>(x);
116128
}
117129

118130
inline constexpr float int32_to_float(std::int32_t x) noexcept
119-
{
120-
return bit_cast<float>(x);
121-
}
131+
{
132+
return bit_cast<float>(x);
133+
}
134+
135+
/**
136+
* @brief Rotates unsigned integer bits to the right.
137+
* https://en.cppreference.com/w/cpp/numeric/rotr
138+
*/
139+
template <class T>
140+
constexpr T rotr(T t, int cnt) noexcept
141+
{
142+
static_assert(ccm::support::traits::is_unsigned_integer_v<T>, "rotr requires an unsigned integer type");
143+
const unsigned int dig = std::numeric_limits<T>::digits;
144+
if ((static_cast<unsigned int>(cnt) % dig) == 0) { return t; }
145+
146+
if (cnt < 0)
147+
{
148+
cnt *= -1;
149+
return (t << (static_cast<unsigned int>(cnt) % dig)) |
150+
(t >> (dig - (static_cast<unsigned int>(cnt) % dig))); // rotr with negative cnt is similar to rotl
151+
}
152+
153+
return (t >> (static_cast<unsigned int>(cnt) % dig)) | (t << (dig - (static_cast<unsigned int>(cnt) % dig)));
154+
}
155+
156+
/**
157+
* @brief Rotates unsigned integer bits to the left.
158+
* https://en.cppreference.com/w/cpp/numeric/rotl
159+
*/
160+
template <class T>
161+
constexpr T rotl(T t, int cnt) noexcept
162+
{
163+
return rotr(t, -cnt);
164+
}
165+
166+
// https://en.cppreference.com/w/cpp/numeric/countr_zero
167+
template <typename T>
168+
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countr_zero(T value)
169+
{
170+
if (value == 0) { return std::numeric_limits<T>::digits; }
171+
172+
if constexpr (ccm::support::traits::is_unsigned_integer_v<T>) { return ccm::support::ctz<T>(value); }
173+
174+
int ret = 0;
175+
const unsigned int ulldigits = std::numeric_limits<unsigned long long>::digits;
176+
while (static_cast<unsigned long long>(value) == 0ULL)
177+
{
178+
ret += ulldigits;
179+
value >>= ulldigits;
180+
}
181+
return ret + ccm::support::ctz(static_cast<unsigned long long>(value));
182+
}
183+
184+
template <typename T>
185+
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countr_one(T value)
186+
{
187+
return value != std::numeric_limits<T>::max() ? countr_zero(static_cast<T>(~value)) : std::numeric_limits<T>::digits;
188+
}
189+
190+
template <typename T, std::enable_if_t<ccm::support::traits::is_unsigned_integer_v<T>, bool> = true>
191+
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countl_zero(T value)
192+
{
193+
if (value == 0) { return std::numeric_limits<T>::digits; }
194+
195+
if constexpr (ccm::support::traits::is_unsigned_integer_v<T>) { return std::numeric_limits<T>::digits - ccm::support::ctz<T>(value); }
196+
197+
int ret = 0;
198+
int iter = 0;
199+
const unsigned int ulldigits = std::numeric_limits<unsigned long long>::digits;
200+
while (true)
201+
{
202+
value = rotl(value, ulldigits);
203+
if ((iter = countl_zero(static_cast<unsigned long long>(value))) != ulldigits) // NOLINT
204+
break;
205+
ret += iter;
206+
}
207+
return ret + iter;
208+
}
209+
210+
template <typename T, std::enable_if_t<ccm::support::traits::is_unsigned_integer_v<T>, bool> = true>
211+
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> countl_one(T value)
212+
{
213+
return value != std::numeric_limits<T>::max() ? countl_zero(static_cast<T>(~value)) : std::numeric_limits<T>::digits;
214+
}
215+
216+
template <typename T>
217+
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> bit_width(T value)
218+
{
219+
return std::numeric_limits<T>::digits - countl_zero(value);
220+
}
221+
222+
#if __has_builtin(__builtin_popcountg)
223+
template <typename T>
224+
[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int> popcount(T value)
225+
{
226+
return __builtin_popcountg(value);
227+
}
228+
#else // !__has_builtin(__builtin_popcountg)
229+
template <typename T>
230+
[[nodiscard]] inline constexpr std::enable_if_t<std::is_unsigned_v<T>, int> popcount(T value)
231+
{
232+
int count = 0;
233+
for (int i = 0; i != std::numeric_limits<T>::digits; ++i)
234+
{
235+
if ((value >> i) & 0x1) { ++count; }
236+
}
237+
return count;
238+
}
239+
#endif // __has_builtin(__builtin_popcountg)
240+
241+
// If the compiler has builtin's for popcount, the create specializations that use the builtin.
242+
#if __has_builtin(__builtin_popcount)
243+
template <>
244+
[[nodiscard]] inline constexpr int popcount<unsigned char>(unsigned char value)
245+
{
246+
return __builtin_popcount(value);
247+
}
248+
249+
template <>
250+
[[nodiscard]] inline constexpr int popcount<unsigned short>(unsigned short value)
251+
{
252+
return __builtin_popcount(value);
253+
}
122254

255+
template <>
256+
[[nodiscard]] inline constexpr int popcount<unsigned>(unsigned value)
257+
{
258+
return __builtin_popcount(value);
259+
}
260+
#endif // __has_builtin(__builtin_popcount)
261+
262+
#if __has_builtin(__builtin_popcountl)
263+
template <>
264+
[[nodiscard]] inline constexpr int popcount<unsigned long>(unsigned long value)
265+
{
266+
return __builtin_popcountl(value);
267+
}
268+
#endif // __has_builtin(__builtin_popcountl)
269+
270+
#if __has_builtin(__builtin_popcountll)
271+
template <>
272+
[[nodiscard]] inline constexpr int popcount<unsigned long long>(unsigned long long value)
273+
{
274+
return __builtin_popcountll(value);
275+
}
276+
#endif // __has_builtin(__builtin_popcountll)
123277

124-
} // namespace ccm::helpers
278+
} // namespace ccm::support
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2024-Present Ian Pike
3+
* Copyright (c) 2024-Present ccmath contributors
4+
*
5+
* This library is provided under the MIT License.
6+
* See LICENSE for more information.
7+
*/
8+
9+
#pragma once
10+
11+
#include <limits>
12+
#include <type_traits>
13+
14+
namespace ccm::support
15+
{
16+
namespace internal
17+
{
18+
// Software implementation of ctz for unsigned integral types in the event that the compiler does not provide a builtin
19+
// Mostly added for msvc support, as gcc and clang have builtins for this.
20+
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>
21+
constexpr int generic_ctz(T x) noexcept
22+
{
23+
// If x is 0, the result is undefined.
24+
if (x == 0)
25+
{
26+
// We return the size of the type in bits to indicate undefined behavior.
27+
// This mimics the behavior of __builtin_ctz.
28+
return sizeof(T) * std::numeric_limits<unsigned char>::digits;
29+
}
30+
31+
int count = 0;
32+
33+
// Loop until the least significant bit is 1
34+
while ((x & 1) == 0)
35+
{
36+
++count;
37+
x >>= 1; // Right shift x by 1 bit
38+
}
39+
40+
return count;
41+
}
42+
} // namespace internal
43+
44+
template <typename T>
45+
constexpr int ctz(T /* x */) noexcept
46+
{
47+
static_assert(false, "Unsupported type for ctz"); // Prevent unsupported types from compiling, but give useful error.
48+
return -1;
49+
}
50+
51+
template <>
52+
constexpr int ctz(unsigned short x) noexcept
53+
{
54+
#if __has_builtin(__builtin_ctzs)
55+
return __builtin_ctzs(x);
56+
#else
57+
return internal::generic_ctz(x);
58+
#endif
59+
}
60+
61+
template <>
62+
constexpr int ctz(unsigned int x) noexcept
63+
{
64+
#if __has_builtin(__builtin_ctz)
65+
return __builtin_ctz(x);
66+
#else
67+
return internal::generic_ctz(x);
68+
#endif
69+
}
70+
71+
template <>
72+
constexpr int ctz(unsigned long x) noexcept
73+
{
74+
#if __has_builtin(__builtin_ctzl)
75+
return __builtin_ctzl(x);
76+
#else
77+
return internal::generic_ctz(x);
78+
#endif
79+
}
80+
81+
template <>
82+
constexpr int ctz(unsigned long long x) noexcept
83+
{
84+
#if __has_builtin(__builtin_ctzll)
85+
return __builtin_ctzll(x);
86+
#else
87+
return internal::generic_ctz(x);
88+
#endif
89+
}
90+
} // namespace ccm::support

0 commit comments

Comments
 (0)