-5

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);
    }
} 
Platin21
  • 3
  • 4
  • Well, you're causing undefined behaviour there. `-I64_MIN` is not representable by your `long long int`. What would you expect the result to be ? – Caninonos Mar 04 '18 at 15:37
  • 2
    I see you expect the answer to be positive, but what numerical value do you expect that to be? – Niall Mar 04 '18 at 15:37
  • Ever heard of [std::abs](http://en.cppreference.com/w/cpp/numeric/math/fabs)? Ever heard of integer overflow? Ever heard of undefined behaviour? – Jesper Juhl Mar 04 '18 at 15:38
  • Why the C++ tag? I see no C++ here, except for `using`. You might as well change it to `typedef` and tag it with C. – Mike Borkland Mar 04 '18 at 15:49
  • @JesperJuhl sadly doesn't work did try it. Retuns: -9223372036854775808 – Platin21 Mar 04 '18 at 15:49
  • 3
    Have you tried using a convertor? You'll quickly find out that 9223372036854775808 is a 65 bit (signed) number, how do you expect it to fit in 64 bits? – Nick is tired Mar 04 '18 at 15:54
  • `9223372036854775808` is not representable as a signed 64 bit value. There must be a dup question somewhere. – Weather Vane Mar 04 '18 at 16:11
  • I do need the number 9223372036854775807 at best without a branch. – Platin21 Mar 04 '18 at 16:12
  • 1
    @MikeBorkland `using` is C++. What's the point of tagging it with a different language than what OP uses? – melpomene Mar 04 '18 at 16:12
  • Probably no one knows a solution without a branch.Sadly need to check with == I64_MAX and then return I64_MIN as it has the same amount of digits it doesn't change anything . – Platin21 Mar 04 '18 at 16:17
  • @Platin21 there *isn't* a solution, with or without a branch, without using a larger type. It is not a bug. – Weather Vane Mar 04 '18 at 16:19
  • 1
    @MikeBorkland The OP is using C++, and he has a question about it. The fact that a completely different language happens to have the same behaviour here is completely irrelevant. Why *should* the OP tag with the wrong language? – Martin Bonner supports Monica Mar 04 '18 at 16:21
  • @KillzoneKid That will turn 1 into -2. Try again. – Martin Bonner supports Monica Mar 04 '18 at 16:28
  • @WeatherVane I'm sure there are multiple dupes of signed/unsigned behaviour at 8/16/32/64 bit limits. In a few years, there will be Q's posted about the 128-bit limits. Such questions are just inevitable from those developers who don't understand how computers work:( – Martin James Mar 04 '18 at 16:33
  • @WeatherVane Probably did ask the wrong thing in some way. I need the I64_MAX from I64_MIN and also for any other value the positiv number. I know that it's not the right value but it has the same amount of digits. (not very good at asking technical questions). – Platin21 Mar 04 '18 at 16:37
  • @KillzoneKid : Nope. 0x80 complemented is 0x7F, incremented is 0x80. Please actually try thinking about your answers. – Martin Bonner supports Monica Mar 04 '18 at 16:40

1 Answers1

1

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.