2

The following integer power function returns 0 when the result should be greater than 2^32 if the base argument is unsigned int, but works fine when the base argument type is changed to an unsigned long. Why? Compiler bug?

#include <cstdint>
#include <iostream>
using uint  = uint32_t ;
using ulong = uint64_t ;

static constexpr ulong ipow(uint base, uint exp, ulong ans = 1) // Integer power
{
    return exp < 1 ? ans : ipow(base*base, exp/2, (exp % 2) ? ans * base : ans) ;
}

int main ()
{
    for (int i(2) ; i < 64 ; ++i)
        std::cout << "ipow(2," << i << ") = " << ipow(2,i) << "\n" ;
    return 0 ;
}

Compiled and ran above code (Apple clang version 17.0.0 (clang-1700.3.19.1) Target: arm64-apple-darwin24.6.0).
Extract of incorrect output:

ipow(2,27) = 134217728
ipow(2,28) = 268435456
ipow(2,29) = 536870912
ipow(2,30) = 1073741824
ipow(2,31) = 2147483648
ipow(2,32) = 0
ipow(2,33) = 0
ipow(2,34) = 0
ipow(2,35) = 0
6
  • 6
    I see that there's also the expression base*base, where base is uint, and I suspect that expression could overflow. Commented Sep 29 at 20:30
  • 3
    ericlippert.com/2014/03/05/how-to-debug-small-programs Commented Sep 29 at 20:40
  • 3
    Ah, right! The squaring of base on every recursion does reach 2^32 after just 5 recursions. Thanks! Commented Sep 29 at 21:00
  • 8
    Tactical note: It's almost never a compiler bug. I recommend adding that tag only after you've posted the question and folks have reviewed it. Commented Sep 29 at 22:55
  • 2
    Removed the compiler-bug tag as it is actually not relevant here. Commented Sep 30 at 5:38

1 Answer 1

10

Why? Compiler bug?

This is not a compiler bug.

The problem is that your base parameter is 32 bit (uint32_t), and so the calculation base*base overflows with values higher than around 2^31.
If you cast the first base to 64 bit it will fix the calculation overflow, but this value is still passed to a 32 bit parameter (base) in the recuresive call and so it will not be preserved.

The solution is to change base parameter type to be 64 bit. This will solve the calculation overflow in your case, as well as preserve the value in the recursive call:

#include <cstdint>
#include <iostream>
using uint  = uint32_t ;
using ulong = uint64_t ;

//--------------------------vvvvv-------------------------------
static constexpr ulong ipow(ulong base, uint exp, ulong ans = 1)
{
    return exp < 1 ? ans : ipow(base*base, exp/2, (exp % 2) ? ans * base : ans) ;
}

int main ()
{
    for (int i(2) ; i < 64 ; ++i)
        std::cout << "ipow(2," << i << ") = " << ipow(2,i) << "\n" ;
}

Output:

ipow(2,2) = 4
ipow(2,3) = 8
ipow(2,4) = 16
.
.
.
ipow(2,61) = 2305843009213693952
ipow(2,62) = 4611686018427387904
ipow(2,63) = 9223372036854775808

Live demo

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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.