Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check division by 3 with binary operations?

I've read this interesting answer about "Checking if a number is divisible by 3"

Although the answer is in Java , it seems to work with other languages also.

Obviously we can do :

boolean canBeDevidedBy3 = (i % 3) == 0;

But the interesting part was this other calculation :

boolean canBeDevidedBy3 = ((int) (i * 0x55555556L >> 30) & 3) == 0;

For simplicity :

0x55555556L = "1010101010101010101010101010110"

Nb

There's also another method to check it :

One can determine if an integer is divisible by 3 by counting the 1 bits at odd bit positions, multiply this number by 2, add the number of 1-bits at even bit positions add them to the result and check if the result is divisible by 3

For example :

9310 ( is divisible by 3)
010111012

It has 2 bits in the odd places and 4 bits at the even places ( place is the zero based of the base 2 digit location)

So 2*1 + 4 = 6 which is divisible by 3.

At first I thought those 2 methods are related but I didn't find how.

Question

How does

  boolean canBeDevidedBy3 = ((int) (i * 0x55555556L >> 30) & 3) == 0;

— actually determines if i%3==0 ?

like image 588
Royi Namir Avatar asked Aug 12 '15 06:08

Royi Namir


People also ask

How do you know if a binary number divides by 3?

Basically count the number of non-zero odd positions bits and non-zero even position bits from the right. If their difference is divisible by 3, then the number is divisible by 3. For example: 15 = 1111 which has 2 odd and 2 even non-zero bits.

How do you check binary division?

Step 1: First, look at the first two numbers in the dividend and compare with the divisor. Add the number 1 in the quotient place. Then subtract the value, you get 1 as remainder. Step 3: Repeat the process until the remainder becomes zero by comparing the dividend and the divisor value.

How do you divide a binary?

The process of binary division is similar to long division in the decimal system. The dividend is still divided by the divisor in the same manner, with the only significant difference being the use of binary rather than decimal subtraction.

Is Division A binary operation?

Addition, subtraction, multiplication, and division are examples of binary operations.


2 Answers

Whenever you add 3 to a number, what you do is to add binary 11. Whatever the original value of the number, this will maintain the invariant that twice the number of 1 bits at odd positions, plus the number of 1 bits at even positions, will also be divisible by 3.

You can see that in this way. Let's call the value of the above expression c. You're adding 1 to an odd position, and 1 to an even position. When you add 1 to an even position, either the bit you've added 1 to was set or unset. If it was unset, you'll increase the value of c by 1, because you've added a new 1 in an odd position. If it was previously set, you'll flip that bit, but add a 1 in an even position (from the carry). This means that you initially decrease c by 1, but now when you add the 1 in the even position, you increase c by 2, so overall you've increased c by 2.

Of course, this carry bit might also get added to a bit that's already set, in which case we need to check that this part still increases c by 2: you'll remove a 1 in an even position (decreasing c by 2), and then add a 1 in an odd position (increasing c by 1), meaning that we've in fact decreased c by 1. That is the same as increasing c by 2, though, if we're working modulo 3.

A more formal version of this would be structured as a proof by induction.

like image 82
chiastic-security Avatar answered Nov 04 '22 03:11

chiastic-security


The two methods do not appear to be related. The bit-wise method seems to be related to certain methods for the efficient computation of modulo b-1 when using digit base b, known in decimal arithmetic as "casting out nines".

The multiplication-based method is directly based on the definition of division when accomplished by multiplication with the reciprocal. Letting / denote mathematical division, we have

int_quot = (int)(i / 3)
frac_quot = i / 3 - int_quot = i / 3 - (int)(i / 3)
i % 3 = 3 * frac_quot = 3 * (i / 3 - (int)(i / 3))

The fractional portion of the mathematical quotient translates directly into the remainder of integer division: If the fraction is 0, the remainder is 0, if the fraction is 1/3 the remainder is 1, if the fraction is 2/3 the remainder is 2. This means we only need to examine the fractional portion of the quotient.

Instead of dividing by 3, we can multiply by 1/3. If we perform the computation in a 32.32 fixed-point format, 1/3 corresponds to 232*1/3 which is a number between 0x55555555 and 0x55555556. For reasons that will become apparent shortly, we use the overestimation here, that is the rounded-up result 0x555555556.

When we multiply 0x55555556 by i, the most significant 32 bits of the full 64-bit product will contain the integral portion of the quotient (int)(i * 1/3) = (int)(i / 3). We are not interested in this integral portion, so we neither compute nor store it. The lower 32-bits of the product is one of the fractions 0/3, 1/3, 2/3 however computed with a slight error since our value of 0x555555556 is slightly larger than 1/3:

i = 1:  i * 0.55555556 = 0.555555556
i = 2:  i * 0.55555556 = 0.AAAAAAAAC
i = 3:  i * 0.55555556 = 1.000000002
i = 4:  i * 0.55555556 = 1.555555558
i = 5:  i * 0.55555556 = 1.AAAAAAAAE

If we examine the most significant bits of the three possible fraction values in binary, we find that 0x5 = 0101, 0xA = 1010, 0x0 = 0000. So the two most significant bits of the fractional portion of the quotient correspond exactly to the desired modulo values. Since we are dealing with 32-bit operands, we can extract these two bits with a right shift by 30 bits followed by a mask of 0x3 to isolate two bits. I think the masking is needed in Java as 32-bit integers are always signed. For uint32_t operands in C/C++ the shift alone would suffice.

We now see why choosing 0x55555555 as representation of 1/3 wouldn't work. The fractional portion of the quotient would turn into 0xFFFFFFF*, and since 0xF = 1111 in binary, the modulo computation would deliver an incorrect result of 3.

Note that as i increases in magnitude, the accumulated error from the imprecise representation of 1/3 affects more and more bits of the fractional portion. In fact, exhaustive testing shows that the method only works for i < 0x60000000: beyond that limit the error overwhelms the most significant fraction bits which represent our result.

like image 33
njuffa Avatar answered Nov 04 '22 02:11

njuffa