简体   繁体   中英

How does this approximation of division using bit shift operations work?

In java.util.DualPivotQuicksort , the following line of code appears:

// Inexpensive approximation of length / 7
int seventh = (length >> 3) + (length >> 6) + 1; 

The variable length is an int greater than or equal to 47.

I am familiar with how the signed right shift operator works. But I do not know why these particular operations result in an approximation of division by 7. Could someone explain please?

>> is bitshift. Every bit you shift right, in effect divides the number of 2.

Therefore, (length >> 3) is length/8 (rounded down), and (length >> 6) is length/64 .

Take (length/8)+(length/64) is approximately length*(1/8+1/64) = length*0.140625 (approximately)

1/7 = 0.142857...

The +1 at the end can be split into +0.5 for each term, so that length/8 is rounded to nearest (instead of down), and length/64 is also rounded to nearest.


In general, you can easily approximate 1/y , where y = 2^n+-1 with a similar bit-shift approximation.

The infinite geometric series is:

1 + x + x^2 + x^3 + ... = 1 / (1 - x)

Multiplying by x:

x + x^2 + x^3 + ... = x/(1 - x)

And substituting x = 1/2^n

1/2^n + 1/2^2n + 1/2^3n + ... = (1/2^n) / (1 - 1/2^n)
1/2^n + 1/2^2n + 1/2^3n + ... = (1/2^n) / ((2^n - 1)/2^n)

1/2^n + 1/2^2n + 1/2^3n + ... = 1 / (2^n - 1)

This approximates y = 2^n - 1 .

To approximate y = 2^n + 1 , substitute x = -1/2^n instead.

- 1/2^n + 1/2^2n - 1/2^3n + ... = (-1/2^n) / (1 + 1/2^n)
1/2^n - 1/2^2n + 1/2^3n - ... = (1/2^n) / ((2^n + 1)/2^n)

1/2^n - 1/2^2n + 1/2^3n - ... = 1 / (2^n + 1)

Then just truncate the infinite series to the desired accuracy.

Set x = 1/8 in the well-known equality

1 + x + x^2 + x^3 + ... = 1 / (1 - x)

and simplify, to give

1/8 + 1/64 + 1/512 + ...  = 1/7

Multiply both sides of this by length in your example, to give

length / 7 = length / 8 + length / 64 + length / 512 + ...

Note that this is "exact" division, not integer division - I'm writing mathematics, not Java code.

Then the approximation assumes that the third and subsequent terms will be too small to matter, and that on average one of length / 8 and length / 64 is likely to need rounding up, rather than rounding down. So, now using integer division, length / 7 = length / 8 + length / 64 + 1 is a very good approximation.

The expression you gave, using bitwise operators, is just an alternative way of writing this, provided length is positive.

To put a mathematical background to ronalchn's answer:

Since 7=8-1=8*(1-1/8), by the geometric series division by 7 is the same as multiplication by

1/7 = 1/8·(1+1/8+1/8²+1/8³+…) = 1/8+1/8²+1/8³+…


To do the same for the division by 5, one would use that 3·5=16-1 and thus

1/5 = 3/16·(1+1/16+1/16²+…)

which would invite a formula like

(3*n)<<4 + (3*n) << 8 + 1

Computing all values of

n/8 + n/64 - n/7

the error grows linearly, while staying negative.

The list below shows the first time a given error appears

n = 7 e = -1
n = 63 e = -2
n = 511 e = -3
n = 959 e = -4
n = 1407 e = -5
n = 1855 e = -6
n = 2303 e = -7
n = 2751 e = -8
n = 3199 e = -9
n = 3647 e = -10
n = 4095 e = -11
n = 4543 e = -12
n = 4991 e = -13
n = 5439 e = -14
n = 5887 e = -15
n = 6335 e = -16
n = 6783 e = -17
n = 7231 e = -18
n = 7679 e = -19
n = 8127 e = -20
n = 8575 e = -21
n = 9023 e = -22
n = 9471 e = -23
n = 9919 e = -24
...

The ratio obviously tends to 1/448 = 1/8 + 1/64 - 1/7 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM