2

I am trying to understand implicit conversion in C .I have the following C code:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int a = -6 ;
    size_t b = 100 ;
    int result = a*b ; /* a is converted to size_t,
    multiply two unsigned ints and cast the result to int
    */
    double dresult = a*b ;
    printf("Result : %d \n",result); //         Output  Result : -600 
    printf("Double Result : %f \n",dresult); // Output  Double Result : 18446744073709551616.000000 
    return 0;
}

From what I understood from cppreference, the result should be some garbage (as printed when converted to double) value, but why does it give me the right value?

bolov
  • 72,283
  • 15
  • 145
  • 224
Aditya Kurrodu
  • 325
  • 2
  • 9
  • 1
    Are you sure that `a` is converted to `size_t`? – mkrieger1 May 27 '23 at 18:47
  • @mkrieger1 In cppreference, it is given as "If the unsigned type has conversion rank greater than or equal to the rank of the signed type, then the operand with the signed type is implicitly converted to the unsigned type. ", so I assume it is converted to size_t – Aditya Kurrodu May 27 '23 at 18:49
  • 1
    "should be some garbage" I'm pretty sure cppreference doesn't say anything similar to this. Can you quote the actual words that made you arrive to this conclusion? – n. m. could be an AI May 27 '23 at 18:55
  • cppreferecne also says *"if the target type is signed, the behavior is implementation-defined"* So you will have to check your compiler's manual to see how the int could become -600. – BoP May 27 '23 at 18:55
  • 3
    `size_t` is unsigned, and your `18446744073709551616` is equivalent to `-600` (2^64 - 600). – Weather Vane May 27 '23 at 19:02
  • @n.m. I was assuming that ```int result = a*b``` will trigger undefined behaviour when ```int``` is assigned with ```18446744073709551616``` If I understood correctly, It is also mentioned in *Real floating-integer conversions* : " If the resulting value can be represented by the target type, that value is used otherwise, the behavior is undefined " – Aditya Kurrodu May 27 '23 at 19:07
  • 3
    "Undefined" doesn't mean "will crash" or "will yield bizarre behavior". It's true that the unsigned number 18446744073709551016 can't be represented as a signed int. But what you typically get (especially on popular two's complement machines) is the same bit pattern, truncated and interpreted as a signed int, which is -600 again. – Steve Summit May 27 '23 at 19:09
  • 1
    That number you got for `dresult` isn't "garbage", either; it's that same number 18446744073709551016, rounded to the 53-bit available precision of type `double`. – Steve Summit May 27 '23 at 19:12
  • Thanks a lot @SteveSummit. What I understood from your comments is that : It does (2^64-a)*b = 2^64*b-a*b. Now 2^64*b is rounded to 2^64, so we get 2^64-a*b as pointed out in the above comment from WeatherVane ? – Aditya Kurrodu May 27 '23 at 19:15
  • @WeatherVane: 18,446,744,073,709,551,616 is 2^64, not 2^64−600. The latter is 18,446,744,073,709,551,016. – Eric Postpischil May 27 '23 at 20:20
  • @AdityaKurrodu: Re “I was assuming that `int result = a*b` will trigger undefined behaviour when int is assigned with `18446744073709551616`. If I understood correctly, It is also mentioned in *Real floating-integer conversions* : " If the resulting value can be represented by the target type, that value is used otherwise, the behavior is undefined”: The conversion of `a*b` to initialize `result` is a conversion from an integer type (likely `size_t`) to an integer type (`int`). It is not a real floating-integer conversion. – Eric Postpischil May 27 '23 at 20:21
  • 1
    @SteveSummit: Conversion from an integer-type out-of-range value to a signed-integer type is implementation-defined, not undefined. – Eric Postpischil May 27 '23 at 20:22
  • @EricPostpischil yes that is true. OP's code comment is that 2^64 was output, and my local compilation also output 2^64, and not 2^64 - 600. Although the compiler did warn about possible loss of data converting `size_t` to `double`, and of course the value 2^64 cannot be held by a 64-bit `size_t`. – Weather Vane May 27 '23 at 20:53
  • 1
    "Undefined behaviour" does not mean the result will be garbage, it means the standard is not telling you anything about the result. – n. m. could be an AI May 27 '23 at 21:45

1 Answers1

4

When evaluating the expression a*b, the usual arithmetic conversions are applied to both operands. This means that

  • on platforms on which size_t has equal or higher rank than int (which is the case on most platforms), a is converted to size_t. Since size_t is unsigned, the value of a (which is -6) will be converted to an unsigned value. On platforms on which size_t is 64-bit, this value will be 18446744073709551610. Multiplying this value of a with the value of b (which is 100) will yield the mathematical result 1844674407370955161000, which is not representable in a 64-bit size_t. Therefore, assuming a 64-bit size_t, the actual result will be the mathematical result modulo 18446744073709551616 (2 to the power 64), which is 18446744073709551016. On platforms on which size_t is 32-bit, the corresponding result is 4294966696. Note that this "overflow" will not invoke undefined behavior, because only signed integer overflow will invoke undefined behavior. Unsigned integer overflow is well-defined.

  • on platforms on which size_t has a rank less than int (which are theoretically possible and permitted by the ISO C standard, but unlikely to exist in practice), a will not be converted to size_t, so the result will be simply -600.

For the reasons stated above, depending on which platform you are using, the result of a*b will likely be either 18446744073709551016, 4294966696 or -600.

The line

double dresult = a*b ;

will therefore likely write either the value 18446744073709551016.0, 4294966696.0 or -600.0 to dresult, depending on which platform you are using. On your platform, the number 18446744073709551016.0 does not seem to be exactly representable in a double due to floating-point inaccuracy, so that it gets rounded to 18446744073709551616.0.

With the line

int result = a*b ;

the situation is not quite as simple. On platforms on which a*b evaluates to -600, it is clear that the value -600 will be written to result. However, on platforms on which a*b evaluates to 18446744073709551016, the problem is that this value is not representable in an int, assuming that an int is 32-bit. In that case, according to §6.3.1.3 ¶3 of the ISO C11 standard, the value written to result is implementation-defined or an implementation-defined signal is raised. However, on most platforms, the result is simply the value that is obtained by repeatedly subtracting the maximum number representable in the unsigned version of the target type, plus one, until the result is in the range representable by the type. Therefore, the result would be 18446744073709551016 - 18446744073709551616, which is -600. This appears to also be how your platform is behaving.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • 1
    To be complete, `double dresult = a*b;` could produce a different result than 18,446,744,073,709,551,616 if `double` were decimal or some other base. However, 2^64−600 is necessarily within the finite `double` range (the C standard requires at least 10^37), so the conversion is defined. The only opportunities I see for undefined behavior are a trap in the conversion to `int` or a `size_t` that is so absurdly wide it exceeds the finite range of `double`. – Eric Postpischil May 27 '23 at 20:17
  • @EricPostpischil: I'm not sure what you mean when you write that `"a trap in the conversion to int"` could cause undefined behavior. If an implementation-defined signal is raised according to [§6.3.1.3 ¶3 of the ISO C11 standard](http://port70.net/%7Ensz/c/c11/n1570.html#6.3.1.3p3), wouldn't that be implementation-defined behavior instead of undefined behavior? Or are you referring to something else? – Andreas Wenzel May 27 '23 at 21:11
  • You are correct; I was thinking of a trap, but the standard requires the implementation to produce an implementation-defined `int` value or an implementation-defined signal, not to trap. – Eric Postpischil May 27 '23 at 23:31