Created
February 3, 2025 02:13
-
-
Save AltimorTASDK/2cdb847904db84fd73f4638e13d933aa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bit> | |
#include <climits> | |
#include <concepts> | |
#include <cstdint> | |
#include <iomanip> | |
#include <iostream> | |
#include <limits> | |
#include <type_traits> | |
#include <utility> | |
constexpr auto to_unsigned(std::integral auto value) | |
{ | |
return static_cast<std::make_unsigned_t<decltype(value)>>(value); | |
} | |
template<typename To, typename From> requires(sizeof(From) == sizeof(To)) | |
constexpr auto bit_cast_any(const From &src) | |
{ | |
return std::bit_cast<To>(src); | |
} | |
template<typename To, typename From> requires(sizeof(From) > sizeof(To)) | |
constexpr auto bit_cast_any(const From &src) | |
{ | |
struct holder { | |
To value; | |
char pad[sizeof(From) - sizeof(To)]; | |
}; | |
return std::bit_cast<holder>(src).value; | |
} | |
template<typename To, typename From> requires(sizeof(From) < sizeof(To)) | |
constexpr auto bit_cast_any(const From &src) | |
{ | |
const struct { | |
From value; | |
char pad[sizeof(To) - sizeof(From)]; | |
} copy = { src }; | |
return std::bit_cast<To>(copy); | |
} | |
template<size_t BitSize> | |
class bitmask { | |
template<size_t OtherBitSize> | |
friend class bitmask; | |
public: | |
static constexpr auto bit_size = BitSize; | |
static constexpr auto byte_size = (BitSize + CHAR_BIT - 1) / CHAR_BIT; | |
private: | |
using word_type = uintptr_t; | |
static constexpr auto word_bits = sizeof(word_type) * CHAR_BIT; | |
static constexpr auto word_count = (byte_size + sizeof(word_type) - 1) / sizeof(word_type); | |
static constexpr auto word_indices = std::make_index_sequence<word_count>{}; | |
word_type words[word_count] = { 0 }; | |
public: | |
constexpr bitmask() = default; | |
template<typename T> | |
explicit(!std::integral<T> || sizeof(T) * CHAR_BIT > BitSize) | |
constexpr bitmask(T value) | |
{ | |
*this = bit_cast_any<bitmask>(value); | |
if constexpr (sizeof(T) * CHAR_BIT > BitSize) | |
constrain_bit_size(); | |
} | |
template<std::same_as<word_type> ...T> requires (sizeof...(T) == word_count) | |
constexpr bitmask(T ...values) : words { values... } | |
{ | |
constrain_bit_size(); | |
} | |
template<size_t NewBitSize> | |
explicit(BitSize > NewBitSize) | |
constexpr operator bitmask<NewBitSize>() const | |
{ | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
return bitmask { get_word(I)... }; | |
}(bitmask<NewBitSize>::word_indices); | |
} | |
template<std::integral T> | |
explicit(byte_size > sizeof(T)) | |
constexpr operator T() const | |
{ | |
return bit_cast_any<T>(*this); | |
} | |
private: | |
constexpr void constrain_bit_size() | |
{ | |
words[word_count-1] &= ~word_type{0} >> (word_bits * word_count - BitSize); | |
} | |
constexpr auto get_word(size_t index) const | |
{ | |
return index >= 0 && index < word_count ? words[index] : 0; | |
} | |
public: | |
constexpr bitmask operator<<(size_t shift) const | |
{ | |
const auto wordshift = shift / word_bits; | |
const auto bitshift = shift % word_bits; | |
const auto invshift = word_bits - bitshift; | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
if (bitshift == 0) { | |
return bitmask { get_word(I - wordshift)... }; | |
} else { | |
return bitmask { (get_word(I - wordshift) << bitshift) | | |
(get_word(I - wordshift - 1) >> invshift)... }; | |
} | |
}(word_indices); | |
} | |
constexpr bitmask operator>>(size_t shift) const | |
{ | |
const auto wordshift = shift / word_bits; | |
const auto bitshift = shift % word_bits; | |
const auto invshift = word_bits - bitshift; | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
if (bitshift == 0) { | |
return bitmask { get_word(I + wordshift)... }; | |
} else { | |
return bitmask { (get_word(I + wordshift) >> bitshift) | | |
(get_word(I + wordshift + 1) << invshift)... }; | |
} | |
}(word_indices); | |
} | |
constexpr bitmask operator&(const bitmask &b) const | |
{ | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
return bitmask { words[I] & b.words[I]... }; | |
}(word_indices); | |
} | |
constexpr bitmask operator|(const bitmask &b) const | |
{ | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
return bitmask { words[I] | b.words[I]... }; | |
}(word_indices); | |
} | |
constexpr bitmask operator^(const bitmask &b) const | |
{ | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
return bitmask { words[I] ^ b.words[I]... }; | |
}(word_indices); | |
} | |
constexpr bitmask operator~() const | |
{ | |
return [&]<auto ...I>(std::index_sequence<I...>) { | |
return bitmask { ~words[I]... }; | |
}(word_indices); | |
} | |
constexpr bool operator==(const bitmask &b) const = default; | |
constexpr bool operator!=(const bitmask &b) const = default; | |
}; | |
// CTAD | |
bitmask(auto value) -> bitmask<sizeof(value) * CHAR_BIT>; | |
template<typename T> | |
concept BitMask = requires(T t) { []<size_t N>(bitmask<N>){}(t); }; | |
constexpr auto operator&(const auto &a, const BitMask auto &b) | |
requires std::convertible_to<decltype(a), decltype(b)> | |
{ return static_cast<decltype(b)>(a) & b; } | |
constexpr auto operator|(const auto &a, const BitMask auto &b) | |
requires std::convertible_to<decltype(a), decltype(b)> | |
{ return static_cast<decltype(b)>(a) | b; } | |
constexpr auto operator^(const auto &a, const BitMask auto &b) | |
requires std::convertible_to<decltype(a), decltype(b)> | |
{ return static_cast<decltype(b)>(a) ^ b; } | |
constexpr auto operator==(const auto &a, const BitMask auto &b) | |
requires std::convertible_to<decltype(a), decltype(b)> | |
{ return static_cast<decltype(b)>(a) == b; } | |
constexpr auto operator!=(const auto &a, const BitMask auto &b) | |
requires std::convertible_to<decltype(a), decltype(b)> | |
{ return static_cast<decltype(b)>(a) != b; } | |
template<std::floating_point Float, std::integral Integral, size_t Shift = 0> | |
requires (std::numeric_limits<Float>::is_iec559 && | |
std::numeric_limits<Float>::radix == 2) | |
constexpr Float integral_to_float(Integral value) | |
{ | |
using float_bitmask = bitmask<sizeof(Float) * CHAR_BIT>; | |
constexpr auto int_sign = std::numeric_limits<Integral>::min(); | |
constexpr auto sig_bits = std::numeric_limits<Float>::digits - 1; | |
constexpr auto sig_mask = ~(~float_bitmask(0) << sig_bits); | |
constexpr auto exp_base = std::numeric_limits<Float>::max_exponent - 1; | |
// Check if type requires explicit leading one in significand | |
constexpr auto one_mask = bitmask(Float{1}) & bitmask(Float{2}); | |
constexpr auto exp_shift = one_mask == 0 ? sig_bits : sig_bits + 1; | |
// Set exponent such that increasing the significand by 1 increases the value by 1 << Shift | |
constexpr auto exp = float_bitmask(exp_base + Shift + sig_bits) << exp_shift; | |
constexpr auto sign = float_bitmask(to_unsigned(int_sign) >> Shift) & sig_mask; | |
constexpr auto scale = exp | one_mask | sign; | |
const auto sig = float_bitmask(to_unsigned(value) >> Shift) & sig_mask; | |
const auto result = bit_cast_any<Float>(scale ^ sig) - bit_cast_any<Float>(scale); | |
if constexpr (Shift + sig_bits >= sizeof(Integral) * CHAR_BIT) | |
return result; | |
else | |
return result + integral_to_float<Float, Integral, Shift + sig_bits>(value); | |
} | |
template<typename Float> | |
[[gnu::noinline]] auto i2f_sw(auto value) | |
{ | |
return integral_to_float<Float>(value); | |
} | |
template<typename Float> | |
[[gnu::noinline]] auto i2f_hw(auto value) | |
{ | |
return static_cast<Float>(value); | |
} | |
template<typename Float> | |
void print_test(auto value) | |
{ | |
std::cout << std::setw(20) << value; | |
std::cout << " -> software " << std::setw(22) << i2f_sw<Float>(value) << std::endl; | |
std::cout << std::setw(20) << ""; | |
std::cout << " hardware " << std::setw(22) << i2f_hw<Float>(value) << std::endl; | |
} | |
int main() | |
{ | |
std::cout << std::fixed << std::setprecision(1); | |
print_test<long double>(12345678901234567890u); | |
print_test<double>(12345678901234567890u); | |
print_test<double>(12345678901234567u); | |
print_test<double>(1234567890123456); | |
print_test<double>(12345678); | |
print_test<float>(12345678); | |
print_test<float>(-123); | |
print_test<float>(-1u); | |
print_test<float>(12345678901234567890u); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment