C learning: a puzzling unsigned number shift problem

C learning: a puzzling unsigned number shift problem

problem background


In the process of doing a fixed-point shift of an algorithm, I encountered a strange problem : right shifting by unsigned and signed numbers respectively, and the results of res1 and res2 are the same no matter what the input is. code show as below:

    int64_t tmp = -16;
    int32_t res1 = (int32_t)(((uint64_t)tmp) >> 1);
    int32_t res2 = (int32_t)(tmp >> 1);
    if (res1 != res2) {
    
    
        printf("res1=%d, res2=%d\n", res1, res2);
    }

According to the previous blog analysis, C learning: analysis and summary of the shift problem of unsigned and signed numbers , this is not in line with science, which is equivalent to the blind analysis of the previous blog.

Validation analysis


Next, I verified the individual shifts of tmp to see if there is a difference between unsigned and signed shifts. code show as below:

    int16_t a = -16;
    printf("0x%hx\n", a);
    a >>= 1;
    printf("0x%hx\n", a);
    printf("%d\n", a);

    a = -16;
    uint16_t b = (uint16_t)a;
    printf("0x%hx\n", b);
    b >>= 1;
    printf("0x%hx\n", b);
    printf("%hu\n", b);

From the results of the auxiliary verification, it can be seen that when shifting by signed number, the sign bit is changing, which shows that the previous blog analysis is correct.

Here comes the problem. . .

Theoretically, in the second line of the first code, the forced unsigned 64-bit conversion **(uint64_t)** is added. The result of the shift should be different from the result of the third line. Why are res1 and res2 equal again?

So, after I further disassembled the first piece of code, I found the answer.

    int64_t tmp = -16;
    int64_t a;
    uint64_t b;
    a = tmp >> 1;
    b = ((uint64_t)tmp) >> 1;
    printf("0x%llx\n", a);
    printf("0x%llx\n", b);

    int32_t res1 = (int32_t)b; // 31 shift
    int32_t res2 = (int32_t)a;
    printf("res1=%d, res2=%d\n", res1, res2);

insert image description here

The result of tmp shifting is indeed different, but the key to the problem is the truncation . When truncating from 64 bits to 32 bits, the lower 32-bit bytes are taken, so the results are consistent. So the more important knowledge points here are to look at the blog C Learning: Analysis of Integer Expansion Problems with Different Bit Widths .

In short, there is indeed a difference in the result after the signed number is shifted, but this difference is covered up after truncation. The key lies in the principle of converting high-bit-width data to low-bit-width data. It should be noted that if there are too many digits shifted, there may still be differences after truncation. For example, in the above code, if tmp is assigned a value of -2147483648 and shifted to the right by 33 bits, the result will have a huge difference.

Another noteworthy problem is that in practice, right shift and left shift are often used to replace multiplication and division by powers of 2, but in signed number scenarios, due to the relationship between negative complements, it is often contrary to expectations. For example, if -2 is shifted two bits to the right, if it is processed as an arithmetic shift, the result is not equal to the quotient divided by 4, which is 0, but -1. Furthermore, when shifting to the right is greater than or equal to two bits, the result is always -1. Therefore, in the scene where the quotient is 0 after shifting, you must pay attention to whether it meets expectations.

References


  1. C Learning: Analysis of Integer Expansion Problems with Different Bit Widths, link
  2. C learning: Analysis and summary of the shift problem of unsigned and signed numbers, link

Guess you like

Origin blog.csdn.net/qq_17256689/article/details/129962079