This is quite subtle.

Every integer literal in your program has a type. Which type it has is regulated by a table in 6.4.4.1:

Suffix Decimal Constant Octal or Hexadecimal Constant none int int long int unsigned int long long int long int unsigned long int long long int unsigned long long int

If a literal number can't fit inside the default int type, it will attempt the next larger type as indicated in the above table. So for regular decimal integer literals it goes like:

Try int

If it can't fit, try long

If it can't fit, try long long .

Hex literals behave differently though! If the literal can't fit inside a signed type like int , it will first try unsigned int before moving on to trying larger types. See the difference in the above table.

So on a 32 bit system, your literal 0x80000000 is of type unsigned int .

This means that you can apply the unary - operator on the literal without invoking implementation-defined behavior, as you otherwise would when overflowing a signed integer. Instead, you will get the value 0x80000000 , a positive value.

bal < INT32_MIN invokes the usual arithmetic conversions and the result of the expression 0x80000000 is promoted from unsigned int to long long . The value 0x80000000 is preserved and 0 is less than 0x80000000, hence the result.