Why It Is Different Between -2147483648 and (Int)-2147483648

Why it is different between -2147483648 and (int)-2147483648

2147483648 doesn't fit into an int or a long on your system, so it's treated as a constant of type unsigned long. (Edit: as ouah pointed out in the comments, it's undefined behaviour in standard C++, but your compiler accepts it as an extension.) Negating an unsigned integer value is possible, but results in another unsigned integer value, never a negative number. Negating 2147483648UL produces 2147483648UL (assuming, as is the case on your system, that unsigned long is a 32 bit type).

Casting that to int produces an implementation-defined result, commonly the result you see, but not necessarily. You can get the result you want without any conversions by writing -2147483647 - 1.

Why is -(-2147483648) = - 2147483648 in a 32-bit machine?

Negating an (unsuffixed) integer constant:

The expression -(-2147483648) is perfectly defined in C, however it may be not obvious why it is this way.

When you write -2147483648, it is formed as unary minus operator applied to integer constant. If 2147483648 can't be expressed as int, then it s is represented as long or long long* (whichever fits first), where the latter type is guaranteed by the C Standard to cover that value.

To confirm that, you could examine it by:

printf("%zu\n", sizeof(-2147483648));

which yields 8 on my machine.

The next step is to apply second - operator, in which case the final value is 2147483648L (assuming that it was eventually represented as long). If you try to assign it to int object, as follows:

int n = -(-2147483648);

then the actual behavior is implementation-defined. Referring to the Standard:

C11 §6.3.1.3/3 Signed and unsigned integers


Otherwise, the new type is signed and the value cannot be represented
in it; either the result is implementation-defined or an
implementation-defined signal is raised.

The most common way is to simply cut-off the higher bits. For instance, GCC documents it as:

For conversion to a type of width N, the value is reduced modulo 2^N
to be within range of the type; no signal is raised.

Conceptually, the conversion to type of width 32 can be illustrated by bitwise AND operation:

value & (2^32 - 1) // preserve 32 least significant bits

In accordance with two's complement arithmetic, the value of n is formed with all zeros and MSB (sign) bit set, which represents value of -2^31, that is -2147483648.

Negating an int object:

If you try to negate int object, that holds value of -2147483648, then assuming two's complement machine, the program will exhibit undefined behavior:

n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647

C11 §6.5/5 Expressions


If an exceptional condition occurs during the evaluation of an
expression (that is, if the result is not mathematically defined or
not in the range of representable values for its type), the behavior
is undefined.

Additional references:

  • INT32-C. Ensure that operations on signed integers do not result in overflow

*) In withdrawed C90 Standard, there was no long long type and the rules were different. Specifically, sequence for unsuffixed decimal was int, long int, unsigned long int (C90 §6.1.3.2 Integer constants).

†) This is due to LLONG_MAX, which must be at least +9223372036854775807 (C11 §5.2.4.2.1/1).

Why does the smallest int, −2147483648, have type 'long'?

In C, -2147483648 is not an integer constant. 2147483648 is an integer constant, and - is just a unary operator applied to it, yielding a constant expression. The value of 2147483648 does not fit in an int (it's one too large, 2147483647 is typically the largest integer) and thus the integer constant has type long, which causes the problem you observe. If you want to mention the lower limit for an int, either use the macro INT_MIN from <limits.h> (the portable approach) or carefully avoid mentioning 2147483648:

printf("PRINTF(d) \t: %d\n", -1 - 2147483647);

Why (int)pow(2, 32) == -2147483648

Assuming int is 32 bits (or less) on your machine, this is undefined behavior.

From the standard, conv.fpint:

A prvalue of a floating-point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.

Most commonly int is 32 bits, and it can represent values in the interval [-2^31, 2^31-1] which is [-2147483648, 2147483647]. The result of std::pow(2, 32) is a double that represents the exact value 2^32. Since 2^32 exceeds the range that can be represented by int, the conversion attempt is undefined behavior. This means that in the best case, the result can be anything.

The same goes for your second example: pow(2, 32) - pow(2, 31) is simply the double representation of 2^31, which (just barely) exceeds the range that can be represented by a 32-bit int.

The correct way to do this would be to convert to a large enough integral type, e.g. int64_t:

std::cout << static_cast<int64_t>(std::pow(2, 32)) << "\n"; // prints 4294967296

Largest decimal literal of type int is 2147483648 or 2147483647?

Note that there are no negative integral literals and Integer.MIN_VALUE is −2147483648. So -2147483648 is parsed as “apply unary minus to 2147483648”. It would be very bad if 2147483648 would not be a valid decimal int literal or you couldn't use an int literal of value Integer.MIN_VALUE directly in your program.

Side note: The JLS defines what is correct. So it is correct by definition. It can be bad, though.

C: Casting minimum 32-bit integer (-2147483648) to float gives positive number (2147483648.0)

In C89 with a 32 bit long, 2147483648L has type unsigned long int (see 3.1.3.2 Integer constants). So once modulo arithmetic has been applied to the unary minus operation, INT32_MIN is the positive value 2147483648 with type unsigned long.

In C99, 2147483648L has type long if long is bigger than 32 bits, or long long otherwise (see 6.4.4.1 Integer constants). So there is no problem and INT32_MIN is the negative value -2147483648 with type long or long long.

Similarly in C89 with long larger than 32 bits, 2147483648L has type long and INT32_MIN is negative.

I guess you're using a C89 compiler with a 32 bit long.

One way to look at it is that C99 fixes a "mistake" in C89. In C99 a decimal literal with no U suffix always has signed type, whereas in C89 it may be signed or unsigned depending on its value.

What you should probably do, btw, is include limits.h and use INT_MIN for the minimum value of an int, and LONG_MIN for the minimum value of a long. They have the correct value and the expected type (INT_MIN is an int, LONG_MIN is a long). If you need an exact 32 bit type then (assuming your implementation is 2's complement):

  • for code that doesn't have to be portable, you could use whichever type you prefer that's the correct size, and assert it to be on the safe side.
  • for code that has to be portable, search for a version of the C99 header stdint.h that works on your C89 compiler, and use int32_t and INT32_MIN from that.
  • if all else fails, write stdint.h yourself, and use the expression in WiSaGaN's answer. It has type int if int is at least 32 bits, otherwise long.


Related Topics



Leave a reply



Submit