Last active
May 4, 2024 17:39
-
-
Save saxbophone/9af0b40ac5a5442a5cce3ac94ceb6f85 to your computer and use it in GitHub Desktop.
Generate and verify safe ranges of integers for to-and-from float-int conversion
This file contains 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
/* | |
* Written by Joshua Saxby ("saxbophone") 2023-03-14 -- saxbophone.com | |
* | |
* This demonstration program for how to derive max/min "safe" integers for | |
* conversion to and from floating point is hereby released into the public domain. | |
*/ | |
#include <limits> // numeric_limits | |
#include <type_traits> // make_signed | |
#include <cstddef> // size_t | |
#include <cstdint> | |
/* | |
* TODO: | |
* for easier traits-detection, we could in future add another helper that allows | |
* detecting whether types of a given bit-width N are supported at compile-time | |
* (so we can conditionally compile said code using something nicer than just | |
* checking if 128-bit exists...) | |
*/ | |
template <std::size_t N> | |
struct get_type_for_bit_width { | |
// bounds-checking for maximum width depends on whether __int128_t is defined or not | |
#ifdef __SIZEOF_INT128__ | |
static_assert(N <= 127, "No integer type available that is wide enough"); | |
#else | |
static_assert(N <= std::numeric_limits<std::intmax_t>::digits, "No integer type available that is wide enough"); | |
#endif | |
using type = typename get_type_for_bit_width<N + 1>::type; | |
}; | |
// NOTE: all these specialisations are -1 bits each to take account for the sign bit | |
// (float mantissa excludes the sign bit, but integer includes it!) | |
template <> | |
struct get_type_for_bit_width<7> { | |
using type = std::int8_t; | |
}; | |
template <> | |
struct get_type_for_bit_width<15> { | |
using type = std::int16_t; | |
}; | |
template <> | |
struct get_type_for_bit_width<31> { | |
using type = std::int32_t; | |
}; | |
template <> | |
struct get_type_for_bit_width<63> { | |
using type = std::int64_t; | |
}; | |
// XXX: apparently, this is the only way to detect if 128-bit types are provided: | |
#ifdef __SIZEOF_INT128__ | |
template <> | |
struct get_type_for_bit_width<127> { | |
using type = __int128_t; | |
}; | |
#endif | |
template <std::size_t N> | |
using SignedForWidth = get_type_for_bit_width<N>::type; | |
template <typename FloatType> | |
constexpr std::size_t Width = std::numeric_limits<FloatType>::digits; | |
template <typename FloatType> | |
constexpr SignedForWidth<Width<FloatType>> MAX_SAFE_INTEGER = ((SignedForWidth<Width<FloatType>>)1 << (Width<FloatType>)) - 1; | |
template <typename FloatType> | |
constexpr SignedForWidth<Width<FloatType>> MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER<FloatType>; | |
#include <iomanip> | |
#include <iostream> | |
#include <cassert> | |
int main() { | |
std::cout << MAX_SAFE_INTEGER<float> << std::endl; | |
std::cout << MIN_SAFE_INTEGER<float> << std::endl; | |
std::cout << MAX_SAFE_INTEGER<double> << std::endl; | |
std::cout << MIN_SAFE_INTEGER<double> << std::endl; | |
// brute-force search proving float conversion is safe | |
for (std::int32_t i = MIN_SAFE_INTEGER<float>; i < MAX_SAFE_INTEGER<float>; ++i) { | |
float f = i; | |
assert((std::int32_t)f == i); | |
} | |
// XXX: see note about providing a nicer way to detect this on line 15 | |
#ifdef __SIZEOF_INT128__ | |
std::cout << std::fixed << std::setprecision(0) << (long double)MAX_SAFE_INTEGER<long double> << std::endl; | |
std::cout << std::fixed << std::setprecision(0) << (long double)MIN_SAFE_INTEGER<long double> << std::endl; | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment