Compiling the following code:
double getDouble() { double value = 2147483649.0; return value; } int main() { printf("INT_MAX: %u\n", INT_MAX); printf("UINT_MAX: %u\n", UINT_MAX); printf("Double value: %f\n", getDouble()); printf("Direct cast value: %u\n", (unsigned int) getDouble()); double d = getDouble(); printf("Indirect cast value: %u\n", (unsigned int) d); return 0; }
Outputs (MSVC x86):
INT_MAX: 2147483647 UINT_MAX: 4294967295 Double value: 2147483649.000000 Direct cast value: 2147483648 Indirect cast value: 2147483649
Outputs (MSVC x64):
INT_MAX: 2147483647 UINT_MAX: 4294967295 Double value: 2147483649.000000 Direct cast value: 2147483649 Indirect cast value: 2147483649
In Microsoft documentation there is no mention to signed integer max value in conversions from double
to unsigned int
.
All values above INT_MAX
are being truncated to 2147483648
when it is the return of a function.
I'm using Visual Studio 2019 to build the program. This doesn't happen on gcc.
Am I doing someting wrong? Is there a safe way to convert double
to unsigned int
?
You can convert an int to an unsigned int . The conversion is valid and well-defined. Since the value is negative, UINT_MAX + 1 is added to it so that the value is a valid unsigned quantity. (Technically, 2N is added to it, where N is the number of bits used to represent the unsigned type.)
A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
A compiler bug...
From assembly provided by @anastaciu, the direct cast code calls __ftol2_sse
, which seems to convert the number to a signed long. The routine name is ftol2_sse
because this is an sse-enabled machine - but the float is in a x87 floating point register.
; Line 17 call _getDouble call __ftol2_sse push eax push OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@ call _printf add esp, 8
The indirect cast on the other hand does
; Line 18 call _getDouble fstp QWORD PTR _d$[ebp] ; Line 19 movsd xmm0, QWORD PTR _d$[ebp] call __dtoui3 push eax push OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@ call _printf add esp, 8
which pops and stores the double value to the local variable, then loads it into a SSE register and calls __dtoui3
which is a double to unsigned int conversion routine...
The behaviour of the direct cast does not conform to C89; nor does it conform to any later revision - even C89 explicitly says that:
The remaindering operation done when a value of integral type is converted to unsigned type need not be done when a value of floating type is converted to unsigned type. Thus the range of portable values is [0, Utype_MAX + 1).
I believe the problem might be a continuation of this from 2005 - there used to be a conversion function called __ftol2
which probably would have worked for this code, i.e. it would have converted the value to a signed number -2147483647, which would have produced the correct result when interpreted an unsigned number.
Unfortunately __ftol2_sse
is not a drop-in replacement for __ftol2
, as it would - instead of just taking the least-significant value bits as-is - signal the out-of-range error by returning LONG_MIN
/ 0x80000000
, which, interpreted as unsigned long here is not at all what was expected. The behaviour of __ftol2_sse
would be valid for signed long
, as conversion of a double a value > LONG_MAX
to signed long
would have undefined behaviour.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With