3

I need a program that returns the number of trailing zeros in the binary rapresentation of a number. I found online a function written in C but I don't understand how it works

This is the function:

unsigned tzr(unsigned x) 
{
    unsigned n; /* number of bits */

    n = 0;
    if (!(x & 0x0000FFFF)) { n += 16; x >>= 16; }
    if (!(x & 0x000000FF)) { n +=  8; x >>=  8; }
    if (!(x & 0x0000000F)) { n +=  4; x >>=  4; }
    if (!(x & 0x00000003)) { n +=  2; x >>=  2; }

    n += (x & 1) ^ 1; // anyway what does this do ? 

    return n;
}

Now I've really tried to understand how this works but I don't get it. I really need someone who could explain it to me, I find this code very complicated.

And about those hexadecimal constants, these are their values:

0x0000FFFF = 65535
0x000000FF = 255
0x0000000F = 15
0x00000003 = 3

Now, why the program uses those values and makes a bitwise AND with the number?

Then I know that if you want to handle big numbers you must
use a while instead of the first if statement, like this:

while (!(x & 0x0000FFFF)) { bits += 16; x >>= 16; } // why should I need this ?

But I don't know why ! What's the difference about using a while instead of an if in this case?

ekans
  • 39
  • 5
  • 2
    Look at the binary representation of those hex numbers, and you should understand. Hex `F` is binary `1111`. – Barmar Jul 23 '17 at 22:24
  • Try it out for various random inputs. Maybe even add output statements to your progrma after each step – M.M Jul 23 '17 at 22:26
  • 1
    "I found something on the street, please explain what it is". That's not how stack overflow works. – too honest for this site Jul 23 '17 at 23:14
  • @Olaf I'm really sorry. I didn't know where to find any help. I posted this because i've seen a lot of post about people posting a code asking someone to help them, so I thought i could do the same – ekans Jul 23 '17 at 23:30
  • n += (x & 1) ^ 1 checks LSB which is the result of shifting or no shifting. After this bit, shifting is not needed that is why, if statement is not used. – MCG Jul 24 '17 at 00:07
  • Checking and testing each bit in a loop will make code slower in case there are more trailing bits. – MCG Jul 24 '17 at 00:22
  • The code is not complicated and you ought to understand it on your own. Decimal representations are irrelevant, try looking at binary ones. Also try to express conditions like `!(x & 0x0000FFFF))` in English using words like `bits from {m} to {n} are {all|not all} {ones|zeroes}`. – n. m. could be an AI Jul 24 '17 at 09:25
  • @Olaf: I think SO is the perfect place for people asking how (valid) code works they found, but don't understand (yet). If you disagree, please provide an alternative place or this kind of discussion. Because often newbies will be confused by something found in an existing (tested and working) codebase, but have a hard time wrapping their head around it. – datenwolf Jul 24 '17 at 09:37
  • @datenwolf: Please read the FAQ. We are **not** a tutoring site, but a Q&A site for **specific** questions. Such questions are by definition too broad, becuase they ask multiple questions at once. Such questions/answers are hardly helpful to other users, which is the fundamental goal of this site, Learning the language would be the first approach to such a problem. And it is not my task to provide an alternative if I point out a rule violation. Thank you for not contributing site quality. – too honest for this site Jul 24 '17 at 13:53

5 Answers5

1

The hexadecimal constants are AND'ed with the value to check whether the last [number] of digits is zero.0x0000FFFF is a number with 16 ones in binary. If the value AND'ed with 0x0000FFFF is equal to 0, you know that the last 16 digits are zeroes (the ifs check for the reverse of that statement). Going further 0x000000FF is a number with 8 ones in binary. The next check is for the last 8 digits, next for 4 digits and the last one for 2 digits as 0x00000003 is 11 in binary. After the checks the numbers are shifted to check whether further digits are also zero. This way we can check for any number of trailing zeroes as the values are powers of 2 and adding them works exactly like working with binary.

Last statement checks for the last digit after all the previous shifting is done - AND with 1 and checking if it's 0 or 1 with a XOR(^).

This program checks numbers with 32 bits. You can change the first if to a while to check larger, e.g. 64-bit, numbers. Another way is to check with 0xFFFFFFFF and then shift 32 bits at once.

Jakub Dąbek
  • 1,044
  • 1
  • 8
  • 17
0

The line n += (x & 1) ^ 1 checks the least significant bit (LSB) of the current state of x. If the LSB is a 1 then (x & 1) yeilds 1 which is then XORed (the caret symbol '^' means to XOR two values) with 1 to give 0 (1 ^ 1 == 0). When x has a 0 in the LSB and is XORed with 1 it yeilds 1 (0 ^ 1 == 1).

amisam
  • 91
  • 6
0

!(x&0x0000FFFF) will be true only when the last 16 bits of x are all 0's. The & is a bitwise and, and 0x0000FFFFF is the number ending in 16 1's. So the result of the and is 0 iff all 16 trailing bits are 0 (and so FALSE and 1 reverses the truth value) because if there is at least one 1 among the last 16, the and with the corresponding 1 in the constant will be 1. So then the and is not 0 (so TRUE and ! reverses the truth value).

So the code says: if the last 16 bits are 1, add 16 to n and throw the last 16 bits away (that is what x >>= 16 does).
The next line says in a similar way: if the last 8 bits of the (possibly shortened x) are 0 ,add 8 to n and throw the rightmost 8 bits away, and so on for 4 and 2 bits as well

The last line adds 1 if the rightmost bit (x&1) is 0, otherwise 0 (1^1 = 0).

So say if the righmost 15 bits are 0, the first if will be false , n remains 0.
The second will be true, as we have more than 8. Tne new x will have 7 0-bits, and n=8.
The third will also be true (we have still 4 or more), so the new x has 3 0-bits after the shift and n=12.
The fourth will also be true (2 or more 0's) so the new x has 1 0-bit and n=14.
The final statement adds 1, so get n=15.

Because we use decreasing powers of 2 we don't need a loop. We get all possible n values this way (except 32, for input x=0, a fully correct function should maybe check for that and early abort.

Henno Brandsma
  • 2,116
  • 11
  • 12
0

n += (x & 1) ^ 1; // anyway what does this do ?

This checks the right-most bit. Either it is set or NOT set.

If it is set, then there is NOT another 0 to add onto the running total of trailing zeros, so n+=0.

If it is NOT set, then there is another 0 to add onto the running total of trailing zeros, so n+=1.

Also, your example does NOT compile, it is missing two ; as follows:

unsigned tzr(unsigned x)
{
    unsigned n; /* number of bits */

    n = 0;
    if (!(x & 0x0000FFFF)) { n += 16; x >>= 16; }
    if (!(x & 0x000000FF)) { n += 8; x >>= 8; }
    if (!(x & 0x0000000F)) { n += 4; x >>= 4 } // won't compile due to missing ;
    if (!(x & 0x00000003)) { n += 2; x >>= 2 } // won't compile due to missing ;

    n += (x & 1) ^ 1; // anyway what does this do ?

    return n;
}

Also, you can always try printing out data, for example, every power of 2 has multiple trailing zeros, but only odd amounts of trailing zeros are incremented by an additional 1 from n += (x & 1) ^ 1;...

cout << tzr(9) << endl << endl; // 1001 (not a power of two )
cout << tzr(8) << endl << endl; // 1000 (8>>2 & 1)^1==1
cout << tzr(4) << endl << endl; // 0100 (4>>2 & 1)^1==0
cout << tzr(2) << endl << endl; // 0010 (   2 & 1)^1==1
cout << tzr(1) << endl << endl; // 0001 (   1 & 1)^1==0

tzr(9) == 0 ==> 0 + (9 & 1) ^ 1 == 0 + 0

tzr(8) == 3 ==> 2 + (8>>2 & 1) ^ 1 == 2 + 1

tzr(4) == 2 ==> 2 + (4>>2 & 1) ^ 1 == 2 + 0

tzr(2) == 1 ==> 0 + (2 & 1) ^ 1 == 0 + 1

tzr(1) == 0 ==> 0 + (1 & 1) ^ 1 == 0 + 0

Program ended with exit code: 0

claytonjwong
  • 814
  • 1
  • 7
  • 13
0

You say, "I need a program that returns the number of trailing zeros in the binary rapresentation of a number." But does it have to be the program you found? Here's an alternative solution that implements tzr() in exactly one line of code,

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

int tzr(int n) { /* --- every time n is even, add 1 and check n/2 --- */
  return ( (n/2)*2 == n? 1+tzr(n/2) : 0 ); }

int main ( int argc, char *argv[] ) { /* --- test driver --- */
  int n = (argc>1? atoi(argv[1]) : 1000);
  printf("tzr(%d) = %d\n", n,tzr(n)); }

Is that any easier to understand?

(P.S. You could use bit masks and shifts instead of my divides and multiplies. That might be a little more efficient, but I thought my way might be a little more straightforward to read.)

John Forkosh
  • 502
  • 4
  • 14
  • I would argue that the description of an inherently arithmetic operation in terms of bitwise operators _or vice versa_ is what increases complexity, not the use of bitwise operators as such. The use of signed arithmetic and the potential stack overflow on zero is mildly uncomfortable, and the explanation as to why a one-counting version equivalent would fail is somewhat subtle, but that is perhaps nitpicking. – doynax Jul 24 '17 at 10:37