Skip to content

Instantly share code, notes, and snippets.

@saxbophone
Last active May 4, 2024 17:39
Show Gist options
  • Save saxbophone/9af0b40ac5a5442a5cce3ac94ceb6f85 to your computer and use it in GitHub Desktop.
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
/*
* 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