10
\$\begingroup\$

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);
 }
}
asked Aug 20, 2020 at 0:00
\$\endgroup\$
2
  • 4
    \$\begingroup\$ Welcome to CodeReview@CR. And thanks for providing the reference for the algorithm/procedure. \$\endgroup\$ Commented Aug 20, 2020 at 1:45
  • \$\begingroup\$ @G. Sliepen I added a check for integral input values. \$\endgroup\$ Commented Aug 23, 2020 at 3:39

1 Answer 1

3
\$\begingroup\$

You have misspelt the typenames from <cstdint> (they are all in the std namespace; it's a portability error to assume they are also in the global namespace).

Although probably only of theoretical interest, given the conditions imposed by the FloatingPoint concept, but the exact-width types are usually a bad choice - prefer std::uint_fast64_t and friends where extra width won't hurt.

I guess that throwing exceptions for infinities and NaNs is a valid design choice (I would prefer to return the value unchanged, like std::ceil() does on IEEE-754 platforms), but this behaviour should be clearly documented somewhere - probably at the start of the header file.

answered Nov 6, 2020 at 15:53
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.