Pregunta

I have a weird bug/error/self stupidity concern. I'm developing a small application in C, and I'm using Visual Studio 2010 SP1. The offending code:

uint64_t sum_squared_X = 65535*65535*64;
int64_t sum_squared_Y = 65535*65535*64;

When debugging, I get these results:

sum_squared_X = 18446744073701163072;
sum_squared_Y = -8388544;

Question is, why? An uint64_t has a maximum value of 2^64-1 or 18446744073709551615, and an int64_t a maximum value of 2^63-1 or 9223372036854775807.

65535*65535*64 = 274869518400, which is lower than both maximums. Then why am I getting these results?

I'm completely lost here, some help would be appreciated.

¿Fue útil?

Solución

Short answer: 65535 is multiplied by 65535 using 32-big signed arithmetic, producing -131,071. This is then multiplied by -64 and converted to uint64_t (creating a larger positive value due to wrapping) or int64_t (preserving the result of multiplying -131,071 by 64).

Long answer:

The type of an unsuffixed integer decimal constant depends on its value. It is the first of this list that can represent its value: int, long int, long long int. (Adding a suffix or using an octal or hexadecimal constant changes the list.) Because these types depend on the C implementation, the behavior depends on the C implementation.

It is likely that, in your machine, int is 32 bits. Therefore, the type of “65535” is int, and so is the type of “64”.

Your expression starts with “65535*65535”. This multiplies 65,535 by 65,535. The mathematical result is 4,924,836,225 (in hex, 0xfffe0001). With a 32-bit signed int, this overflows the representable values. This is undefined behavior in the C standard. What commonly happens in many implementations is that the value “wraps around” from 231-1 (the highest representable value) to -231 (the lowest representable value). Another view of the same behavior is that the bits of the mathematical result, 0xfffe0001, are interpreted as the encoding of a 32-bit signed int. In two’s complement, 0xffffe0001 is -131,071.

Then your expression multiplies by 64. -131,071*64 does not overflow; the result is -8,388,544 (with encoding 0xff800040).

Finally, you use the result to initialize a uint64_t or int64_t object. This initialization causes a conversion to the destination type.

The int64_t conversion is straightforward; the input to the conversion is -8,388,544, and this is exactly representable in int64_t, so the result is -8,388,544, which the compiler likely implements simply by extending the sign bit (producing the encoding 0xffffffffff800040).

The uint64_t conversion is troublesome, because -8,388,544 cannot be represented in a uint64_t. According to the 1999 C standard, 6.3.1.3 2, “the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.” For uint64_t, “one more than the maximum value that can be represented in the new type” is 264. So the result is -8,388,544 + 264, which is 18,446,744,073,701,163,072. This also has the encoding 0xffffffffff800040.

For conversion from a narrower width to a wider width, this operation of adding one more than maximum value is equivalent to copying the sign bit of the old type to all higher bits in the new type (sign extension). For conversion from a wider width to a narrower width, it is equivalent to discarding the high bits. In either case, the result is the residue modulo 2n, where n is the number of bits in the new type.

Otros consejos

When I compile your example, I clearly get an integer constant overflow warning for each of those lines. This is because the right side, the constants, is usually stored in a basic integer. You have to change the storage of those values to keep an overflow condition from happening. To fix this, do the following instead:

uint64_t sum_squared_X = (uint64_t)65535*65535*64;
int64_t sum_squared_Y = (uint64_t)65535*65535*64;

Read more here

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top