Как работает это приближение деления с использованием операций битового сдвига?
на java.util.DualPivotQuicksort
, появляется следующая строка кода:
// Inexpensive approximation of length / 7
int seventh = (length >> 3) + (length >> 6) + 1;
переменная length
это int
больше или равно 47.
Я знаком с тем, как работает подписанный оператор правой смены. Но я не знаю, почему эти конкретные операции приводят к приближению деления на 7. Кто-нибудь может объяснить?
4 ответов
>>
является bitshift. Каждый бит, который вы сдвигаете вправо, фактически делит число 2.
таким образом, (length >> 3)
is length/8
(округляется вниз), и (length >> 6)
is length/64
.
взять (length/8)+(length/64)
примерно length*(1/8+1/64)
= length*0.140625
(приблизительно)
1/7 = 0.142857...
на +1
в конце можно разделить на +0.5
для каждого термина, так что length/8
округляется до ближайшего (вместо вниз), и length/64
также округляется до ближайший.
в общем, вы можете легко приблизительные 1/y
, где y = 2^n+-1
с аналогичным приближением битового сдвига.
бесконечный геометрический ряд:
1 + x + x^2 + x^3 + ... = 1 / (1 - x)
умножение на x:
x + x^2 + x^3 + ... = x/(1 - x)
и заменить 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)
это приближает y = 2^n - 1
.
для приблизительного y = 2^n + 1
заменить .
- 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)
тогда просто усечь бесконечное серия с требуемой точностью.
Set x = 1/8
в известном равенстве
1 + x + x^2 + x^3 + ... = 1 / (1 - x)
и упростить, чтобы дать
1/8 + 1/64 + 1/512 + ... = 1/7
умножьте обе стороны этого на length
в вашем примере, чтобы дать
length / 7 = length / 8 + length / 64 + length / 512 + ...
обратите внимание, что это" точное " деление, а не целочисленное деление - я пишу математику, а не Java-код.
тогда приближение предполагает, что третий и последующие члены будут слишком малы, чтобы иметь значение, и что в среднем один из length / 8
и length / 64
скорее всего нужно округлять вверх, а не вниз. Итак, теперь, используя целочисленное деление,length / 7 = length / 8 + length / 64 + 1
очень хорошее приближение.
выражение, которое вы дали, используя побитовые операторы, является просто альтернативным способом написания этого, при условии length
положительный.
чтобы поставить математический фон для ответа рональхна:
Так как 7=8-1=8*(1-1/8), по геометрическому ряду деление на 7 равно умножению на
1/7 = 1/8·(1+1/8+1/82+1/83+...) = 1/8+1/82+1/83+...
сделать то же самое для деления на 5, можно было бы использовать, что 3·5=16-1 и таким образом
1/5 = 3/16·(1+1/16+1/162+...)
который пригласил бы формулу, как
(3*n)<<4 + (3*n) << 8 + 1
вычисление всех значений
n/8 + n/64 - n/7
ошибка растет линейно, оставаясь отрицательной.
список ниже показывает первый раз, когда данная ошибка появляется
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
...
соотношение явно тяготеет к 1/448 = 1/8 + 1/64 - 1/7
.