Search code examples
c++constexprsqrt

Compile-time what is the largest value that can be squared without overflow (integral types)?


In C++, is there a compile-time way to compute the largest value of an integral type T that can be safely squared, i.e. that mathematically x * x <= std::numeric_limits<T>::max() so that if the operation x*x were to be performed, it would not cause undefined behavior (for signed types) or overflow (for unsigned types?

I'm fine restricting to only integral types that have specializations in std::numeric_limits, i.e. no need to worry about new user-defined integral types, if that makes it any easier.

Mathematically, of course, the answer is floor(sqrt(std::numeric_limits<T>::max())). For example, this would be floor(sqrt(2^63-1)) == 3037000499 for a 64-bit signed long long. A (correct even for large numbers) constexpr sqrt would do the trick, though I don't know if that is the best way.

Answers should be:

  • 64-bit unsigned: floor(sqrt(2^64-1)) == 4294967295
  • 64-bit signed: floor(sqrt(2^63-1)) == 3037000499
  • 32-bit unsigned: floor(sqrt(2^32-1)) == 65535
  • 32-bit signed: floor(sqrt(2^31-1)) == 46340

Solution

    • Set the lower half of the bits.
    • For signed types, add 1 and then divide with sqrt(2)
    #include <climits>
    #include <limits>
    #include <type_traits>
    
    template<class T>
    constexpr T largest_squareable() {
        static_assert(std::is_integral_v<T>, "Must be an integral type");
    
        if constexpr (std::is_unsigned_v<T>) {
            return std::numeric_limits<T>::max() >> sizeof(T) * CHAR_BIT / 2;
        } else {        
            constexpr long double sqrt_2 = 1.41421356237309504880L;
            return (largest_squareable<std::make_unsigned_t<T>>() + 1) / sqrt_2;    
        }
    }
    

    Demo

    Or just define the values by hand since there are a very limited number of sizes:

    #include <cstdint>
    
    template<typename T>
    constexpr T largest_squareable();
    
    template<> constexpr int8_t largest_squareable<int8_t>() { return 11; }
    template<> constexpr uint8_t largest_squareable<uint8_t>() { return 15; }
    template<> constexpr int16_t largest_squareable<int16_t>() { return 181; }
    template<> constexpr uint16_t largest_squareable<uint16_t>() { return 255; }
    template<> constexpr int32_t largest_squareable<int32_t>() { return 46340L; }
    template<> constexpr uint32_t largest_squareable<uint32_t>() { return 65535UL; }
    template<> constexpr int64_t largest_squareable<int64_t>() { return 3037000499LL; }
    template<> constexpr uint64_t largest_squareable<uint64_t>() { return 4294967295ULL; }