【问题标题】:Bitwise Multiply and Add in JavaJava中的按位乘法和加法
【发布时间】:2011-06-21 04:16:30
【问题描述】:

我有同时进行乘法和加法的方法,但我无法理解它们。它们都来自外部网站,而不是我自己的:

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 中的按位运算。

【问题讨论】:

  • 乘法一和你中学学的纸笔算法非常相似,只是二进制。它使用了 001011*X = 001000*X + 000010*X + 000001*X 的事实。

标签: java bit-manipulation bitwise-operators multiplication addition


【解决方案1】:

让我们从查看乘法代码开始。这个想法实际上非常聪明。假设您有 n1 和 n2 以二进制形式编写。那么你可以把 n1 看作是 2 的幂的和:n2 = c30 230 + c29 229 + ... + c1 21 + c0 20,其中每个 ci 是 0 或 1。那么您可以将乘积 n1 n2 视为

n1 n2 =

n1 (c30 230 + c29 229 + ... + c1 21 + c0 20) =

n1 c30 230 + n1 c29 2 29 + ... + n1 c1 21 + n1 c0 20

这有点密集,但想法是这两个数字的乘积是第一个数字乘以构成第二个数字的 2 的幂,再乘以第二个数字的二进制数字的值。

现在的问题是我们是否可以在不进行任何实际乘法的情况下计算这个总和的项。为此,我们需要能够读取 n2 的二进制数字。幸运的是,我们可以使用轮班来做到这一点。特别是,假设我们从 n2 开始,然后只看最后一位。那是 c0。如果我们然后将值向下移动一个位置,那么最后一位是 c0,等等。更一般地,在将 n2 的值向下移动 i 个位置之后,最低位将是 ci。要读取最后一位,我们可以将数值与数字 1 逐位与。这有一个二进制表示,除了最后一位之外,所有地方都为零。由于任何 n 的 0 AND n = 0,这将清除所有最高位。此外,由于 0 AND 1 = 0 和 1 AND 1 = 1,此操作保留了数字的最后一位。

好的 - 我们现在知道我们可以读取 ci 的值;所以呢?好消息是,我们也可以用类似的方式计算序列 n1 2i 的值。特别是,考虑 n11

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's 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);
}

希望这会有所帮助!

【讨论】:

  • 只有 1 个简单的问题:为什么我们在每次迭代后都添加 n1*2^i?
  • @user183037- 除非设置了 n2 的当前位,否则我们不会添加 n1 * 2^i。但是,我们确实将 a 的值放大了两倍,以便在下一次迭代开始时它的值是 n1 * 2^{i+1}。这能说明问题吗?
  • 如果 b 是负数,由于“>>”运算符的性质,这将不起作用。应该使用 b >>>=1 代替。
  • 值得注意的是,有几件事可以大大减少所有按位相加所需的时间。可以很容易地重写一个数字,以便将每一对位替换为从 -2 到 +2 的值 [+3 或 -3 将由 -1 或 +1 以及进位或借入/借出下一个地方],从而将所需的添加数量减少了一半。更重要的是,a+b+c 可以计算为 (a^b^c) + ((a&b | b&c | a&c)
  • ...可以使用上面的公式 N-2 次将整个输入字符串转换为两个需要相加的数字。
【解决方案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 操作添加两个一位数。使用 and 你现在可以找出你是否有一个“进位”位,这与用纸笔加数字非常相似。 (到目前为止,你有一个叫做 Half-Adder 的东西)。当您添加接下来的两位时,您还需要将进位位添加到这两位数。考虑到这一点,您可以获得全加器。您可以在 Wikipedia 上阅读有关半加器和全加器的概念: http://en.wikipedia.org/wiki/Adder_(electronics) 以及网络上更多的地方。 我希望这能给你一个开始。

顺便说一句,乘法非常相似。只要记住你在小学时是如何用笔和纸做乘法的。这就是这里发生的事情。只是它发生在二进制数而不是十进制数上。

【讨论】:

    【解决方案3】:

    按位加法的解释:

    我知道不久前有人问过这个问题,但由于没有给出关于 bitwiseAdd 方法如何在这里工作的完整答案。

    理解bitwiseAdd中封装的逻辑的关键在于addition运算与xorand位运算的关系。该关系由以下等式定义(有关该等式的数字示例,请参见附录 1):

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

    或者更简单地说:

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

    您可能已经注意到这个等式中有一些熟悉的东西:andxor 变量与 bitwiseAdd 开头定义的变量相同。还有一个乘以 2,在 bitwiseAdd 中是在 while 循环的开头完成的。但我稍后会谈到这一点。

    在我们继续之前,我还要简要说明一下'&'位运算符。这个运算符基本上“捕获”了它所应用的位序列的交集。例如,9 & 13 = 1001 & 1101 = 1001 (=9)。您可以从此结果中看到,只有两个位序列共有的那些位被复制到结果中。由此得出,当两个位序列没有共同位时,对它们应用'&'的结果是0这对加法-位关系有重要影响,很快就会清楚

    现在我们遇到的问题是方程 1.2 使用了 '+' 运算符,而 bitwiseAdd 没有(它只使用了 '^'、'&' 和 'and 表达式返回 0。我们这样做的方式是使用 recursion

    为了证明这一点,我将递归方程 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 中发现的那样。当您考虑 and[1]xor[1] 是什么时,这种联系变得更加明显:它们与 and 是相同的表达式和 xor 表达式定义了 INSIDE 在 bitwiseAdd 中的 while 循环。我们还注意到出现了一种模式:方程 1.4 看起来与方程 1.2 完全相同!

    因此,如果保留递归符号,则进行进一步的递归是轻而易举的事。这里我们将方程 1.2 再递归两次:

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

    现在应该突出显示 bitwiseAdd 中的“temp”变量的作用:temp 允许从一个递归级别传递到下一个递归级别。

    我们还注意到在所有这些等式中乘以 2。如前所述,此乘法是在 bitwiseAdd 的 while 循环开始时使用 and 语句完成的。这种乘法会对下一个递归阶段产生影响,因为 and[i] 中的位与前一阶段的 and[i] 中的位不同(如果您还记得我之前关于 '&' 运算符的小注释您可能会看到现在的情况)。

    方程 1.4 的一般形式现在变为:

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

    最后:

    那么这个递归业务到底什么时候结束呢?

    答案:当等式1.5的and[x]表达式中两个位序列的交集返回0时结束。当 while 循环条件变为 false 时,会发生 bitwiseAdd 中的等效情况。此时方程 1.5 变为:

        x + y = xor[x]      (1.6)
    

    这就解释了为什么在 bitwiseAdd 中我们只在最后返回 xor

    我们完成了!这是一段非常聪明的代码,我必须说:)

    希望对你有帮助

    附录:

    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)
    

    为了得到这个结果,我们简单地将上面等式 1.2 的 2* 和 + xor 部分应用到等式 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)
    

    用方程 1.2 的 andxor 变量的定义来简化这个,得到方程 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);
      }
      

      【讨论】:

        猜你喜欢
        • 2021-04-12
        • 1970-01-01
        • 2021-03-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-07
        • 1970-01-01
        相关资源
        最近更新 更多