3
int main()
{
    printf("Hello World\n");
    int x = -10;
    unsigned y = 25;
    float z = x*y;
    printf("x=%d,y=%u,z=%f\n",x,y,z);
    return 0;
}

When I run the above code, I get the following output:

Hello World
x=-10,y=25,z=4294967046.000000

My question is: For the second printf, I would have expected z=(float) ( (unsigned)(-10)*25 ) = (float) (4294967286 x 25) = (float) 107374182150, what am I missing here?

Aniket Tiratkar
  • 798
  • 6
  • 16
Vikas Yadav
  • 322
  • 2
  • 9

2 Answers2

5

Here's what's happening. As per C11 6.3.1.8 Usual arithmetic conversions (the "otherwise" comes into play here since previous paragraphs discuss what happens when either type is already floating point):

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

This means your signed value of -10 becomes an unsigned value of 0xffff'fff6, or 4,294,967,286. Multiplying that by 25 gives 107,374,182,150 or 0x18'ffff'ff06 (which is the result you want).

However, at this point, no float calculations have been done, the multiplication is a pure integer calculation and the resultant value will be an integer. And that, combined with the fact your unsigned integers are 32 bits long, means it gets truncated to 0xffff'ff06, or 4,294,967,046.

Then you put that into the float.


To fix this to match your expected results, you should change he expression to force this:

float z = 1.0f * (unsigned)x * y;

This changes the int * unsigned-int calculation into a float * unsigned-int * unsigned-int one. The unsigned cast first ensures x will be converted to the equivalent unsigned value and the multiplication by 1.0f ensures the multiplication are done in the float arena to avoid integer truncation.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
3

Following on from the correct answer from @paxdiablo, the starting point for the result is due to unsigned having a rank equal to the rank of the int, e,g,

The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any. C11 Standard - 6.3.1 Arithmetic operands(p1)

This comes into play with the integer conversion cited in @paxdiablo's answer:

6.3.1.8 Usual arithmetic conversions

  • Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
    • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

The problem is that -10 (negative values) are stored (in almost all computers) in two-complement. In two's complement the value for -10 takes the bitwise NOT of 10 and adds 1 (so in binary 00001010 become 11110110 sign extended to 32-bits). That is:

11111111111111111111111111110110

For which the unsigned values is 4294967286. When multiplied by 25, it exceeds the range of unsigned so the value is reduced modulo until it fits within the range of unsigned resulting in 4294967046. 6.2.5 Types(p9).

What Am I Missing?

The part that is missing is understanding the result of unsigned multiplication is being assigned as a float value. The intermediate result from x * y is unsigned. float f = x * y; is just an assignment of the result to a float.

What you want is for the intermediate calculation to be done as a float, so cast one of the operands (not the result) to float, e.g.

float f = (float)x * y

It does not matter which of the two values is cast to float, the following would be just fine:

float f = x * (float)y;

Now the result will be -250.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • 2
    Just one minor nitpick, David, I *think* (though I could be wrong) it's complement (as in complementary) rather than compliment (as in "you look good today"). And moves are afoot in both ISO C and C++, to bless the two's complement method as the *only* signed encoding scheme going forward. – paxdiablo Oct 27 '20 at 03:10
  • 1
    Well, you do look good today -- but you are also right there -- spell check didn't save me.. Will fix. – David C. Rankin Oct 27 '20 at 03:13
  • Thanks @DavidC.Rankin for chiming in. The part that i missed (overlooked) was that 32-bit mult has 32-bit output (unsigned wrapped around till it fits in as you mentioned). – Vikas Yadav Oct 27 '20 at 04:10
  • 1
    Glad it helped. There is a lot there, and it takes going back to the standard a number of times to cement it -- and even then you will end up making trips back to the standard -- just to make sure `:)` – David C. Rankin Oct 27 '20 at 04:33