C: Casting minimum 32-bit integer (-2147483648) to float gives positive number (2147483648.0)
I was working on an embedded project when I ran into something which I thought was strange behaviour. I managed to reproduce it on codepad (see below) to confirm, but don't have any other C compilers on my machine to try it on them.
Scenario: I have a #define
for the most negative value a 32-bit integer can hold, and then I try to use this to compare with a floating point value as shown below:
#define INT32_MIN (-2147483648L)
void main()
{
float myNumber = 0.0f;
if(myNumber > INT32_MIN)
{
printf("Everything is OK");
}
else
{
printf("The universe is broken!!");
}
}
Codepad link: http://codepad.org/cBneMZL5
To me it looks as though this this code should work fine, but to my surprise it prints out The universe is broken!!
.
This code implicitly casts the INT32_MIN
to a float
, but it turns out that this results in a floating point value of 2147483648.0
(positive!), even though the floating point type is perfectly capable of representing -2147483648.0
.
Does anyone have any insights into the cause of this behaviour?
CODE SOLUTION: As Steve Jessop mentioned in his answer, limits.h
and stdint.h
contain correct (working) int
range define
s already, so I'm now using these instead of my own #define
PROBLEM/SOLUTION EXPLANATION SUMMARY: Given the answers and discussions, I think this is a good summary of what's going on (note: still read the answers/comments because they provide a more detailed explanation):
- I'm using a C89 compiler with 32-bit
long
s, so any values greater thanLONG_MAX
and less or equal toULONG_MAX
followed by theL
postfix have a type ofunsigned long
-
(-2147483648L)
is actually a unary-
on anunsigned long
(see previous point) value:-(2147483648L)
. This negation operation 'wraps' the value around to be theunsigned long
value of2147483648
(because 32-bitunsigned long
s have the range0
-4294967295
). - This
unsigned long
number looks like the expected negativeint
value when it gets printed as anint
or passed to a function because it is first getting cast to anint
, which is wrapping this out-of-range2147483648
around to-2147483648
(because 32-bitint
s have the range -2147483648 to 2147483647) - The cast to
float
, however, is using the actualunsigned long
value2147483648
for conversion, resulting in the floating-point value of2147483648.0
.
Replace
#define INT32_MIN (-2147483648L)
with
#define INT32_MIN (-2147483647 - 1)
-2147483648
is interpreted by the compiler to be the negation of 2147483648
, which causes overflow on an int
. So you should write (-2147483647 - 1)
instead.
This is all C89
standard though. See Steve Jessop's answer for C99
.
Also long
is typically 32 bits on 32-bit machines, and 64 bits on 64-bit machines. int
here gets the things done.
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 useint32_t
andINT32_MIN
from that. - if all else fails, write
stdint.h
yourself, and use the expression in WiSaGaN's answer. It has typeint
ifint
is at least 32 bits, otherwiselong
.