ceil_constexpr is is based on: https://stackoverflow.com/questions/8377412/ceil-function-how-can-we-implement-it-ourselves/8378022#8378022
ceil_constexpr2 is a simpler version that takes advantage of truncation.
WARNING 1: Both ceil functions uses from c++20.
WARNING 2: ceil_constexpr uses bit_cast - a c++20 function that I believe only MSVC v14.27 supports at the current date, 20th August 2020.
#include <cstdint>
#include <concepts>
#include <limits>
#include <bit>
#include <exception>
template<typename T>
concept FloatingPoint =
std::is_floating_point_v<T> &&
(sizeof(T) == 4 || sizeof(T) == 8) &&//Only 32/64 bit allowed. 80 bit fp not allowed
sizeof(float) == 4 && sizeof(double) == 8 &&//float must be 32 bit fp while double must be 64 bit fp
std::numeric_limits<T>::is_iec559 == true &&// Only IEEE 754 fp allowed
std::endian::native == std::endian::little;
template<FloatingPoint T>
constexpr bool isInf_constexpr(T inFp)//detect if infinity or -infinity
{
constexpr bool is_T_Float = std::is_same_v<T, float>;
using uintN_t = std::conditional_t<is_T_Float, uint32_t, uint64_t>;
using intN_t = std::conditional_t<is_T_Float, int32_t, int64_t>;
constexpr uintN_t mantissaBitNumber = is_T_Float ? 23 : 52;
constexpr uintN_t infinityExponentValue = is_T_Float ? 0xff : 0x7ff; //the value of the exponent if infinity
constexpr uintN_t positiveInfinityValue = infinityExponentValue << mantissaBitNumber;//the value of positive infinity
constexpr uintN_t signRemovalMask = std::numeric_limits<intN_t>::max();//the max value of a signed int is all bits set to one except sign
return ((std::bit_cast<uintN_t, T>(inFp) & signRemovalMask) == positiveInfinityValue);//remove sign before comparing against positive infinity value
}
template<FloatingPoint T>
constexpr bool isNaN_constexpr(T inFp)
{
constexpr bool is_T_Float = std::is_same_v<T, float>;
using uintN_t = std::conditional_t<is_T_Float, uint32_t, uint64_t>;
using intN_t = std::conditional_t<is_T_Float, int32_t, int64_t>;
constexpr uintN_t mantissaBitNumber = is_T_Float ? 23 : 52;
constexpr uintN_t NaNExponentValue = is_T_Float ? 0xff : 0x7ff;//the value of the exponent if NaN
constexpr uintN_t signRemovalMask = std::numeric_limits<intN_t>::max();//the max value of a signed int is all bits set to one except sign
constexpr uintN_t exponentMask = NaNExponentValue << mantissaBitNumber;
constexpr uintN_t mantissaMask = (~exponentMask) & signRemovalMask;//the bits of the mantissa are 1's, sign and exponent 0's.
return (
((std::bit_cast<uintN_t, T>(inFp) & exponentMask) == exponentMask) &&//if exponent is all 1's
((std::bit_cast<uintN_t, T>(inFp) & mantissaMask) != 0) //if mantissa is != 0
);
}
template<FloatingPoint T>
constexpr T ceil_constexpr(T inFp)
{
if (isInf_constexpr<T>(inFp))
{
throw std::invalid_argument("Input floating point is infinity.");
}
else if (isNaN_constexpr<T>(inFp))
{
throw std::invalid_argument("Input floating point is NaN.");
}
constexpr bool is_T_Float = std::is_same_v<T, float>;
constexpr uint32_t mantissaBitNumber = is_T_Float ? 23 : 52;
constexpr uint32_t exponentMask = is_T_Float ? 255 : 2047;//used to remove the sign bit after the exponent bits
constexpr uint32_t exponentBias = is_T_Float ? 127 : 1023;
using uintN_t = std::conditional_t<is_T_Float, uint32_t, uint64_t>;
using intN_t = std::conditional_t<is_T_Float, int32_t, int64_t>;
const uintN_t input = std::bit_cast<uintN_t, T>(inFp);//bitwise copy floating point to unsigned integer
const intN_t exponent = static_cast<intN_t>((input >> mantissaBitNumber) & exponentMask) - exponentBias;
if (exponent < 0)
{
return (inFp > 0);
}
// small numbers get rounded to 0 or 1, depending on their sign
const intN_t fractional_bits = static_cast<intN_t>(mantissaBitNumber) - exponent;
if (fractional_bits <= 0)
{
return inFp;
}
// numbers without fractional bits are mapped to themselves
//constexpr uintN_t uIntAllOnes = is_T_Float ? 0xffffffff : 0xffffffffffffffff;
constexpr uintN_t uIntAllOnes = std::numeric_limits<uintN_t>::max();//store the max value of an unsigned integer (all bits are 1's)
const uintN_t integral_mask = uIntAllOnes << fractional_bits;
const uintN_t output = input & integral_mask;
// round the number down by masking out the fractional bits
inFp = std::bit_cast<T, uintN_t>(output);//bitwise copy unsigned integer to floating point
if (inFp > 0 && output != input)
{
++inFp;
}
// positive numbers need to be rounded up, not down
return inFp;
}//algorithm from: https://stackoverflow.com/questions/8377412/ceil-function-how-can-we-implement-it-ourselves/8378022#8378022
template<FloatingPoint T>
constexpr T ceil_constexpr2(const T inFp)//simpler version
{
if (isInf_constexpr<T>(inFp))
{
throw std::invalid_argument("Input floating point is infinity.");
}
else if (isNaN_constexpr<T>(inFp))
{
throw std::invalid_argument("Input floating point is NaN.");
}
constexpr bool is_T_Float = std::is_same_v<T, float>;
using uintN_t = std::conditional_t<is_T_Float, uint32_t, uint64_t>;
using intN_t = std::conditional_t<is_T_Float, int32_t, int64_t>;
if (inFp > 0 && inFp != static_cast<intN_t>(inFp))
{
return static_cast<intN_t>(inFp + 1);
}
else
{
return static_cast<intN_t>(inFp);
}
}