прямолинейного способ, 8 операций (другие являются операциями по константам):
M = (1<<(N-S)) - 1; // A mask with S lowest bits.
q = ( ((p & (M<<(2*N+S))) >> (3*S)) // Mask 'i', shift to new position.
+ ((p & (M<<( N+S))) >> (2*S)) // Likewise for 'j'.
+ ((p & (M<< S)) >> S)); // Likewise for 'k'.
выглядит сложным, но на самом деле это не так, просто нелегко (по крайней мере, мне), чтобы все константы исправить.
Чтобы создать формулу с меньшим количеством операций, заметим, что смещение чисел на U
бит влево совпадает с умножением на 1<<U
. Таким образом, из-за размножения умножения умножение на ((1<<U1) + (1<<U2) + ...)
такое же, как смещение влево на U1
, U2
, ... и добавление всего вместе.
Таким образом, мы могли бы попытаться замаскировать необходимые части из i
, j
и k
, «сдвиг», чтобы они все правильные позиции по отношению друг к другу с одним умножением, а затем перенести результат вправо, к конечному пункту назначения. Это дает нам три операции для вычисления q
от p
.
К сожалению, существуют ограничения, особенно для случая, когда мы пытаемся получить все три одновременно. Когда мы добавляем числа вместе (косвенно, добавляя несколько множителей), мы должны убедиться, что биты могут быть установлены только в одном числе, иначе мы получим неправильный результат. Если мы пытаемся добавить (косвенно) три должным образом смещенными числа сразу, мы имеем следующее:
iiiii...........jjjjj...........kkkkk.......
N-S S N-S S N-S
.....jjjjj...........kkkkk................
N-S N-S S N-S
..........kkkkk...............
N-S N-S N-S
Обратите внимание, что дальше налево во второй и третий номера являются биты i
и j
, но игнорировать их. Для этого предположим, что умножение работает как на x86: , умножая два типа. T
дает номер типа T
, с наименьшими битами фактического результата (равным результату, если нет переполнения).
Таким образом, чтобы убедиться, что k
биты из третьих чисел не совпадают с j
битами из первых, нам нужно, чтобы 3*(N-S) <= N
, т.е. S >= 2*N/3
, который для N = 8
ограничивает нас S >= 6
(всего один или два бита на компонент после сдвига , не знаю, используете ли вы эту низкую точность).
Однако, если S >= 2*N/3
, мы можем использовать только три операции:
// Constant multiplier to perform three shifts at once.
F = (1<<(32-3*N)) + (1<<(32-3*N+S)) + (1<<(32-3*N+2*S));
// Mask, shift/combine with multipler, right shift to destination.
q = (((p & ((M<<(2*N+S)) + (M<<(N+S)) + (M<<S))) * F)
>> (32-3*(N-S)));
Если ограничение для S
является слишком строгим (что это вероятно), мы можем объединить первую и вторую формулу: вычислить i
и k
со вторым подходом, затем добавьте j
из первой формулы. Здесь нам нужны, чтобы биты не перекрывались в следующих номерах:
iiiii...............kkkkk.......
N-S S N-S S N-S
..........kkkkk...............
N-S N-S N-S
I.e. 3*(N-S) <= 2*N
, что дает S >= N/3
, или, для N = 8
гораздо менее строгий S >= 3
. Формула выглядит следующим образом:
// Constant multiplier to perform two shifts at once.
F = (1<<(32-3*N)) + (1<<(32-3*N+2*S));
// Mask, shift/combine with multipler, right shift to destination
// and then add 'j' from the straightforward formula.
q = ((((p & ((M<<(2*N+S)) + (M<<S))) * F) >> (32-3*(N-S)))
+ ((p & (M<<(N+S))) >> (2*S)));
Эта формула работает для примера, где S = 4
.
Является ли это более быстрым, чем простой подход, зависит от архитектуры. Кроме того, я понятия не имею, поддерживает ли C++ предполагаемое поведение переполнения умножения. Наконец, вам нужно удостовериться, что значения без знака и точно 32 бит для того, чтобы формулы работали.
Не должно быть 'q = ((i >> S) << (2 * (N-S))) + ((j >> S) <<(N-S)) + (k>> S)'? В противном случае может быть недостаточно бит. – interjay
Полностью верно, и я задал вопрос. Благодаря! – Danvil
Постоянны ли 'N' и' S'? То есть влияет ли она на скорость функции уменьшения масштаба, если она вычисляет сложные выражения, содержащие 'N' и/или' S'? – doublep