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.