Побитовое умножение и добавление в Java

У меня есть методы, которые выполняют как умножение, так и добавление, но я просто не могу обойти их вокруг. Оба они связаны с внешними веб-сайтами, а не моими:

    public static void bitwiseMultiply(int n1, int n2) {
        int a = n1, b = n2, result=0;
        while (b != 0) // Iterate the loop till b==0
        {
            if ((b & 01) != 0) // Logical ANDing of the value of b with 01
            {
                result = result + a; // Update the result with the new value of a.
            }
            a <<= 1;              // Left shifting the value contained in 'a' by 1.
            b >>= 1;             // Right shifting the value contained in 'b' by 1.
        }
        System.out.println(result);
    }

    public static void bitwiseAdd(int n1, int n2) {
        int x = n1, y = n2;
        int xor, and, temp;
        and = x & y;
        xor = x ^ y;

        while (and != 0) {
            and <<= 1;
            temp = xor ^ and;
            and &= xor;
            xor = temp;
        }
        System.out.println(xor);
    }

Я попытался сделать пошаговую отладку, но для меня это не имело большого смысла, хотя оно работает.

То, что я, возможно, ищу, - это попытаться понять, как это работает (возможно, математическая основа?).

Изменить: это не домашнее задание, я просто пытаюсь изучить побитовые операции в Java.

Ответ 1

Давайте начнем с просмотра кода умножения. Идея на самом деле довольно умная. Предположим, что у вас есть n 1 и n 2, записанные в двоичном формате. Тогда вы можете думать о n1 как сумме степеней двух: n2 = c 30 2 30 + c 29 2 29 +... + c 1 2 1 + c 0 2 0 где каждый c i является либо 0, либо 1. Тогда вы можете думать о продукте n 1 n 2 как

n 1 n 2=

n 1 (c 30 2 30 + c 29 2 29 +... + c 1 2 1 + c 0 2 0) =

n 1 c 30 2 30 + n 1 c 29 2 29 +... + n 1 c 1 2 1 + n 1 c 0 2 0

Это немного плотно, но идея состоит в том, что произведение двух чисел задается первым числом, умноженным на две степени, составляющими второе число, умноженное на значение двоичных цифр второго числа.

Теперь возникает вопрос, можем ли мы вычислить члены этой суммы без каких-либо реальных умножений. Для этого нам нужно будет прочитать двоичные цифры n 2. К счастью, мы можем сделать это с помощью сдвигов. В частности, предположим, что мы начинаем с n 2, а затем просто смотрим на последний бит. Это c 0. Если мы затем сдвинем значение вниз на одну позицию, то последний бит будет c 0 и т.д. В более общем плане, после смещения значения n 2 вниз по позициям i, младший бит будет c i. Чтобы прочитать последний бит, мы можем просто поразрядное И значение с номером 1. Это имеет двоичное представление, равное нулю всюду, кроме последней цифры. Поскольку 0 AND n = 0 для любого n, это очищает все самые верхние биты. Более того, поскольку 0 AND 1 = 0 и 1 AND 1 = 1, эта операция сохраняет последний бит числа.

Хорошо - теперь мы знаем, что мы можем прочитать значения c i; И что? Хорошо, хорошая новость заключается в том, что мы также можем вычислить значения серии n 1 2 i аналогичным образом. В частности, рассмотрим последовательность значений n 1 < < 0, n 1 < 1 и т.д. Каждый раз, когда вы выполняете левый бит-сдвиг, это эквивалентно умножению на две. Это означает, что теперь мы имеем все компоненты, необходимые для вычисления указанной суммы. Вот ваш исходный исходный код, прокомментировал, что происходит:

public static void bitwiseMultiply(int n1, int n2) {
    /* This value will hold n1 * 2^i for varying values of i.  It will
     * start off holding n1 * 2^0 = n1, and after each iteration will 
     * be updated to hold the next term in the sequence.
     */
    int a = n1;

    /* This value will be used to read the individual bits out of n2.
     * We'll use the shifting trick to read the bits and will maintain
     * the invariant that after i iterations, b is equal to n2 >> i.
     */
    int b = n2;

    /* This value will hold the sum of the terms so far. */
    int result = 0;

    /* Continuously loop over more and more bits of n2 until we've
     * consumed the last of them.  Since after i iterations of the
     * loop b = n2 >> i, this only reaches zero once we've used up
     * all the bits of the original value of n2.
     */
    while (b != 0)
    {
        /* Using the bitwise AND trick, determine whether the ith 
         * bit of b is a zero or one.  If it a zero, then the
         * current term in our sum is zero and we don't do anything.
         * Otherwise, then we should add n1 * 2^i.
         */
        if ((b & 1) != 0)
        {
            /* Recall that a = n1 * 2^i at this point, so we're adding
             * in the next term in the sum.
             */
            result = result + a;
        }

        /* To maintain that a = n1 * 2^i after i iterations, scale it
         * by a factor of two by left shifting one position.
         */
        a <<= 1;

        /* To maintain that b = n2 >> i after i iterations, shift it
         * one spot over.
         */
        b >>>= 1;
    }

    System.out.println(result);
}

Надеюсь, это поможет!

Ответ 2

Похоже, ваша проблема не в java, а в простом вычислении с двоичными числами. Начало простого: (все числа двоичные:)

0 + 0 = 0   # 0 xor 0 = 0
0 + 1 = 1   # 0 xor 1 = 1
1 + 0 = 1   # 1 xor 0 = 1
1 + 1 = 10  # 1 xor 1 = 0 ( read 1 + 1 = 10 as 1 + 1 = 0 and 1 carry)

Хорошо... Вы видите, что вы можете добавить два однозначных числа, используя операцию xor. С помощью а теперь вы можете узнать, есть ли у вас бит "carry", который очень похож на добавление чисел с помощью ручки и бумаги. (До этого момента у вас есть что-то, называемое Half-Adder). Когда вы добавляете следующие два бита, вам также нужно добавить бит переноса к этим двум цифрам. Учитывая это, вы можете получить Full-Adder. Вы можете прочитать о понятиях Half-Adders и Full-Adders в Википедии: http://en.wikipedia.org/wiki/Adder_(electronics) И еще много мест в Интернете. Надеюсь, это даст вам начало.

С умножением это очень похоже. Просто помните, как вы умножались с ручкой и бумагой в начальной школе. Вот что здесь происходит. Просто, что это происходит с двоичными числами, а не с десятичными числами.

Ответ 3

ОБЪЯСНЕНИЕ МАТЕМАТИКА bitwiseAdd:

Я знаю, что этот вопрос был задан некоторое время назад, но так как никакого полного ответа не было дано относительно того, как метод bitwiseAdd работает здесь.

Ключ к пониманию логики, инкапсулированной в bitwiseAdd, находится в отношениях между операциями добавление и xor и и побитными операциями. Это соотношение определяется следующим уравнением (см. Приложение 1 для численного примера этого уравнения):

x + y = 2 * (x&y)+(x^y)     (1.1)

Или проще:

x + y = 2 * and + xor       (1.2)

with
    and = x & y
    xor = x ^ y

Возможно, вы заметили что-то знакомое в этом уравнении: переменные и и xor совпадают с теми, которые определены в начале bitwiseAdd. Существует также умножение на два, которые в bitwiseAdd выполняются в начале цикла while. Но я вернусь к этому позже.

Позвольте мне также сделать короткую заметку о '&' побитовый оператор, прежде чем двигаться дальше. Этот оператор в основном "фиксирует" пересечение битовых последовательностей, с которыми он применяется. Например, 9 и 13 = 1001 и 1101 = 1001 (= 9). Вы можете видеть из этого результата, что только те биты, общие для обеих битовых последовательностей, копируются в результат. Из этого вытекает, что, когда две битовые последовательности не имеют общего бита, результат применения '&' на них получается 0. Это имеет важное значение для добавления-побитового отношения, которое скоро станет ясно.

Теперь проблема заключается в том, что уравнение 1.2 использует оператор '+', тогда как bitwiseAdd не использует (он использует только '^', '&' и '< <'). Итак, как же заставить "+" в уравнении 1.2 как-то исчезнуть? Ответ: "принудительно" выражение и возвращает 0. И как мы это делаем, используя рекурсию.

Чтобы продемонстрировать это, я собираюсь повторить уравнение 1.2 один раз (этот шаг может быть немного сложным сначала, но при необходимости там подробный шаг за шагом результат в приложении 2):

x + y = 2*(2*and & xor) + (2*and ^ xor)     (1.3)

Или проще:

x + y = 2 * and[1] + xor[1]     (1.4)

with
    and[1] = 2*and & xor,
    xor[1] = 2*and ^ xor,
    [1] meaning 'recursed one time'

Здесь есть несколько интересных вещей. Сначала вы заметили, как концепция рекурсии кажется близкой к концепции цикла, как на самом деле, найденной в bitwiseAdd. Это соединение становится еще более очевидным, если вы считаете, что и [1] и xor [1]: они являются теми же выражениями, что и и и xor, определенные INSIDE цикл while в bitwiseAdd. Отметим также, что появляется картина: уравнение 1.4 выглядит точно так же, как уравнение 1.2!

В результате этого дальнейшие рекурсии - это бриз, если сохранить рекурсивную нотацию. Здесь мы повторяем уравнение 1.2 еще два раза:

x + y = 2 * and[2] + xor[2]
x + y = 2 * and[3] + xor[3]

Теперь следует выделить роль переменной temp, найденной в bitwiseAdd: temp позволяет перейти от одного уровня рекурсии к следующему.

Мы также замечаем умножение на два во всех этих уравнениях. Как упоминалось ранее, это умножение выполняется в начале цикла while в bitwiseAdd с помощью оператора и <= lt <= 1. Это умножение имеет последствия на следующем этапе рекурсии, так как биты in и [i] отличаются от бит в и [i] предыдущего этапа (и если вы вспомните маленькую заметку, которую я сделал ранее о & оператор, вероятно, вы увидите, где это происходит сейчас).

Общий вид уравнения 1.4 теперь становится следующим:

x + y = 2 * and[x] + xor[x]     (1.5)
with x the nth recursion

Finaly:

Итак, когда заканчивается этот рекурсивный бизнес?

Ответ: он заканчивается, когда пересечение между двумя битовыми последовательностями в выражении и [x] уравнения 1.5 возвращает 0. Эквивалент этого в bitwiseAdd происходит, когда условие цикла while становится ложным. В этот момент уравнение 1.5 становится:

    x + y = xor[x]      (1.6)

И это объясняет, почему в bitwiseAdd мы возвращаем xor в конце!

И мы закончили! Довольно умный кусок кода, этот bitwiseAdd я должен сказать:)

Я надеюсь, что это помогло

ПРИЛОЖЕНИЕ:

1) Численный пример уравнения 1.1

уравнение 1.1 говорит:

    x + y = 2(x&y)+(x^y)        (1.1)

Чтобы проверить это утверждение, можно взять простой пример, например добавить 9 и 13 вместе. Этапы показаны ниже (побитовые представления приведены в скобках):

Мы имеем

    x = 9 (1001)
    y = 13  (1101)

и

    x + y = 9 + 13 = 22
    x & y = 9 & 13 = 9 (1001 & 1101 = 1001)
    x ^ y = 9^13 = 4 (1001 ^ 1101 = 0100)

вернувшись в уравнение 1.1, находим:

    9 + 13 = 2 * 9 + 4 = 22 et voila!

2) Демонстрация первого этапа рекурсии

Первое уравнение рекурсии в изложении (уравнение 1.3) говорит, что

если

x + y = 2 * and + xor           (equation 1.2)

затем

x + y = 2*(2*and & xor) + (2*and ^ xor)     (equation 1.3)

Чтобы получить этот результат, мы просто взяли часть 2 * и + xor уравнения 1.2 выше и применили отношение добавления/поразрядных операндов, заданное уравнением 1.1, Это. Это демонстрируется следующим образом:

если

    x + y = 2(x&y) + (x^y)      (equation 1.1)

затем

     [2(x&y)] + (x^y)     =      2 ([2(x&y)] & (x^y)) + ([2(x&y)] ^ (x^y))
(left side of equation 1.1)  (after applying the addition/bitwise operands relationship)

Упрощение этого с помощью определений переменных и и xor уравнения 1.2 дает результат 1.3:

[2(x&y)] + (x^y) = 2*(2*and & xor) + (2*and ^ xor)

with
    and = x&y
    xor = x^y

И используя то же упрощение, получаем результат уравнения 1.4:

2*(2*and & xor) + (2*and ^ xor) = 2*and[1] + xor[1]

with
    and[1] = 2*and & xor
    xor[1] = 2*and ^ xor
    [1] meaning 'recursed one time'

Ответ 4

Вот еще один подход для умножения

/**
 * Multiplication of binary numbers without using '*' operator
 * uses bitwise Shifting/Anding
 *
 * @param n1
 * @param n2
 */
public static void multiply(int n1, int n2) {
    int temp, i = 0, result = 0;

    while (n2 != 0) {
        if ((n2 & 1) == 1) {
            temp = n1;

            // result += (temp>>=(1/i)); // To do it only using Right shift
            result += (temp<<=i); // Left shift (temp * 2^i)
        }
        n2 >>= 1;   // Right shift n2 by 1.
        i++;
    }

    System.out.println(result);
}