Search code examples
c++macosnumbersintsigned

Can not flip sign


I found a weird bug that happens when i try to flip the sign of the number -9223372036854775808, which does simply nothing. I get the same number back or at least that's what the debugger shows me. Is there a way to solve this without branching?

#define I64_MAX  9223372036854775807LL
#define I64_MIN  (-I64_MAX-1) 
// -9223372036854775808 (can not be a constant in code as it will turn to ull)

using i64 = long long int;

int main()
{
 i64 i = I64_MIN;
 i = -i;
 printf("%lld",i);
 return 0;
}

Does the same thing with i32,i16,i8.


EDIT:
Current Fix:

// use template??
c8* szi32(i32 num,c8* in)
{
    u32 number = S(u32,num);
    if(num < 0)
    {
        in[0] = '-';
        return SerializeU32(number,&in[1]);
    }
    else
    {
        return SerializeU32(number,in);
    }
} 

Solution

  • You can't do it in a completely portable way. Rather than dealing with int64_t, let us consider int8_t. The principle is almost exactly the same, but the numbers are much easier to deal with. I8_MAX will be 127, and I8_MIN will be -128. Negating I8_MIN will give 128, and there is no way to store that in int8_t.

    Unless you have strong evidence that this is a bottleneck, then the right answer is:

    constexpr int8_t negate(int8_t i) {
        return (i==I8_MIN) ? I8_MAX : -i;
    }
    

    If you do have such evidence, then you will need to investigate some platform dependent code - perhaps a compiler intrinsic of some sort, perhaps some clever bit-twiddling which avoids a conditional jump.


    Edit: Possible branchless bit-twiddling

    constexpr int8_t negate(int8_t i) {
        const auto ui = static_cast<uint8_t>(i); 
        // This will calculate the two's complement negative of ui.
        const uint8_t minus_ui = ~ui+1;
        // This will have the top bit set if, and only if, i was I8_MIN
        const uint8_t top_bit = ui & minus_ui;
        // Need to get top_bit into the 1 bit.  Either use a compiler intrinsic rotate:
        const int8_t bottom_bit = static_cast<int8_t>(rotate_left(top_bit)) & 1;
        // -or- hope that your implementation does something sensible when you
        // shift a negative number (most do).
        const int8_t arithmetic_shifted = static_cast<int8_t>(top_bit) >> 7;
        const int8_t bottom_bit = arithmetic_shifted & 1;
        // Either way, at this point, bottom_bit is 1 if and only if i was
        // I8_MIN, otherwise it is zero.
        return -(i+bottom_bit);
    }
    

    You would need to profile to determine whether that is actually faster. Another option would be to shift top_bit into the carry bit, and use add-with-carry (adding a constant zero), or write it in assembler, and use an appropriate conditionally executed instruction.