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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.