Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I don't really know why bit-shifting works in one scenario but not the other [duplicate]

Tags:

c

bit-shift

So I am doing some bit-shifting and i have come to the following problem, that i would be more than grateful to get an answer to:

As an argument I'm allowed to pass the size of 1 byte.

The first 4 bits represent a numerator. The last 4 bits represent the denominator.

The following code works and gives the correct output:

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

int main(int argc, char** argv)
{
    for(int i = 1; i < argc; i++)
    {
        unsigned char numerator = atoi(argv[i]);
        numerator = (numerator >> 4);
        numerator = (numerator << 4);

        unsigned char denominator = atoi(argv[i]);
        denominator = (denominator << 4);
        denominator = (denominator >> 4);
        printf("%d/%d\n", numerator, denominator);
    }

    return 0;
}

But if i substitute the bit shifting part like this, the denominator gives the same output as the numerator:

unsigned char numerator = atoi(argv[i]);
        numerator = (numerator >> 4) << 4;

unsigned char denominator = atoi(argv[i]);
        denominator = (denominator << 4) >> 4;

sample input would be:

./test 1
./test 16

output given:

0/1
16/16

expected output:

0/1
16/0

Thanks in advance for any sort of help.

like image 654
Sh1r Avatar asked Sep 05 '25 05:09

Sh1r


2 Answers

When arithmetic expressions are evaluated in C, any integral types smaller than int are promoted to int before the calculations are performed.


So this code:

unsigned char denominator = atoi(argv[i]);
denominator = (denominator << 4);
denominator = (denominator >> 4);

results in the following sequence (each box represents 4 bits, and the text in the box is a hex digit):

enter image description here

Note that the assignment denominator = (denominator << 4); forces the compiler to convert the value back to an unsigned char. Then on the next line of code, the compiler needs to promote the unsigned char back to an int.


But this code:

unsigned char denominator = atoi(argv[i]);
denominator = (denominator << 4) >> 4;

skips the conversion back to unsigned char and the second promotion to int. So the sequence is:

enter image description here

Note that the final value is the same as the original value, since the shifting was performed using 32-bits, and nothing was shifted off the left side of the 32-bit value.


The numerator doesn't have the same problem. Because the numerator is shifted right first, the bits on the right are lost. The promotion to int has no effect on the results. Here's the sequence for the numerator:

enter image description here

like image 149
user3386109 Avatar answered Sep 07 '25 21:09

user3386109


It seems that the values are being cast to a larger integer type (such as int), and when the shifts are performed, the upper bits are being preserved in the larger int type, then the result is cast back to unsigned char after both shifts are complete.

I would use a bitmask like this, instead of shifting:

...
unsigned char data = atoi(argv[i]);
unsigned char numerator = data & 0xf0;
unsigned char denominator = data & 0xf;
...
like image 26
Willis Hershey Avatar answered Sep 07 '25 19:09

Willis Hershey