Search code examples
cgcctype-promotion

Usual Arithmetic Conversion in Arithmetic Operation


I am currently learning about type conversion in C, and I came across the concept of Usual Arithmetic Conversions in implicit type conversion.

Implicit type conversion, in which the C compiler automatically converts the type without the programmer's involvement, is what I have understood: if the type is narrow, it is promoted to the wider type.

First Experiment with Relational Operators

#include <stdio.h>

int main(void){

    unsigned long int a  = 8888;
    unsigned long int c = 0;
    signed int b = -1;

    if(a > b){
        c = 10;
    }else {
        c = 7;
    }
    
    printf("C value is %lu\n",c);
    
}

In the code provided, the value of c ends up being 7 even though mathematically, a is larger than b, and we would expect c to be 10. enter image description here

Why doesn't this happen? This lies in the type promotion. Because here, the signed type is a smaller type (Narrow) compared to the unsigned type, which is larger (wider), so it gets promoted => which causes the type promotion, so the signed type is converted to unsigned type implicitly, which means now b is 2^32 around 4 billion so that the if statement is false and the else is executed.

For relational operators, it is ok.

Second Experiment with Arithmetic Operators

#include <stdio.h>

int main(void){

    unsigned long int a  = 8888;
    unsigned long int c;
    signed int b = -1;

    c = b + a;
    
    printf("C value is %lu\n",c);
    
}

enter image description here

For me this was happen

  1. Why does the relational operator (a > b) not behave as I expected? Specifically, why does the comparison seem to treat -1 (signed) as a large number instead of a small one?

For this relational operator, implicit conversion happens means why don't they happen for the arithmetic operation?

  1. In the arithmetic operation b + a, why do I get a result of 8887 instead of a larger number like 4294976184? Does this involve overflow, or is it another issue with how the signed value is promoted?

Solution

    1. … Specifically, why does the comparison seem to treat -1 (signed) as a large number instead of a small one?

    Arithmetic in C largely follows what is available in processors. Processors generally have instructions to work with numbers in the same format. For example, there may be an instruction to add two eight-bit signed integers, another instruction to add two 16-bit signed integers, another to add two 32-bit signed integers, and similarly instructions for multiplying, dividing, and comparing numbers. (Sometimes these instructions take advantage of mathematical properties so that one instruction can serve multiple purposes. For example, adding two eight-bit numbers can be performed by adding two 32-bit numbers but ignoring the top 24 bits. But this is another topic.)

    Processors generally do not have an instruction that compares a 32-bit unsigned integer to an eight-bit signed integer. Adding such an instruction would require adding more wires and switches to the processor, and that is not worthwhile for an operation that would be rarely used. Without a specific instruction for this, C could provide the comparison operation, but it would have to use multiple instructions. We could do it by comparing the eight-bit integer to zero. If it is less than zero, it is also less than the unsigned integer. Otherwise, we could then convert the eight-bit integer to a 32-bit unsigned integer (just by putting it in a larger register, with the high 24 bits set to zero) and using the instruction to compare two 32-bit unsigned integers.

    But that is more work than most comparisons. C is largely designed to facilitate and encourage efficient code. The rules about arithmetic conversions in C expressions were designed to work with typical processors. Programmers who want to evaluate expressions differently need to write their own code for that.

    1. In the arithmetic operation b + a, why do I get a result of 8887 instead of a larger number like 4294976184? Does this involve overflow, or is it another issue with how the signed value is promoted?

    For illustration, I will assume your C implementation uses 32 bits for unsigned long int. When you add the unsigned long int value 8888 (22B816 in hexadecimal) and the signed int value −1, the signed int is converted to unsigned long int, which produces 4,294,967,295 (FFFFFFFF16). That is because conversion to a 32-bit unsigned integer is defined to wrap modulo 232 = 4,294,967,296.

    Adding 22B816 and FFFFFFFF16 is mathematically 1000022B716, but that is 33 bits. Unsigned integer arithmetic is also defined to wrap, so that 232 bit is eliminated, and the result is 000022B716 = 8887.