【问题标题】:Why this code for addition(using bitwise operation) works in java为什么这个加法代码(使用按位运算)在java中工作
【发布时间】:2013-06-27 11:34:31
【问题描述】:
public int add(int a, int b){  
        while (b != 0){
            int carry = (a & b) ;

            a = a ^ b; 

            b = carry << 1;
        }
        return a;
 }

这是使用按位运算计算两个整数之和的代码。

如果我手动/以编程方式计算,我发现它适用于每个整数。 但我无法弄清楚acarry 的中间值之间的任何关系。 为什么进位乘以 2 分配给b

PS:我在这里找到了一个答案 Bitwise Multiply and Add in Java 但这是用于乘法而不是用于加法。

【问题讨论】:

  • 你知道按位加法吗?
  • 一个数字的进位被添加到下一个数字,当然。这个算法只是执行“你从学校知道的加法”,但有点混淆而不是仔细地逐个数字地进行 - 它先进行加法而不先进位(异或),然后分别添加龋齿(再次以相同的方式)直到不再有进位(因此它添加了通过添加进位生成的进位,然后是该添加生成的进位,依此类推)。这必须终止,因为进位只会“向左” - 所以每次迭代后至少有一个位是“固定的”。
  • @smsnheck 是的,我有,你的意思是什么?
  • @harold 非常感谢..实际上我发现很难相信下一位结转二进制文件是如何工作的,因为它从一开始就没有一点一点发生..
  • @Sunny 我想解释一下它是如何工作的,并想知道你的水平。所以对我来说解释起来会更容易(而不是写你已经知道的东西)。

标签: java bit-manipulation bitwise-operators


【解决方案1】:

小学时第一次回忆加法。例如26 + 147 = 173。从 6+7=13 开始,因此将 3 放入总和中,然后进位,依此类推 - 即:添加两位数并在必要时进位。

carry:  1
a:      26
b:     147
-----------------
sum:   173

代码在二进制数上的作用几乎相同,但稍作调整。它不是一次占据一个数字位置,而是一次完成。代码不是在 i 中包含来自位置 i-1 的进位(即在添加 2 和 4 时包含 1),而是在第二次迭代中添加所有龋齿。所以它的作用是:026+147 = 163 + 010 = 173 + 000

对于二进制数 a=6=00110 和 b=7=00111 你得到

首先你找到carry;这是ab 都设置了位的所有位置:int carry = (a &amp; b) ;

然后 id 进行数字相加,忽略进位,并将其存储在 a 中:a = a ^ b; 这将在示例中响应 6+7=3

最后一部分将进位移动到下一个数字位置,即确保示例中的 1 进位从 1“移动”到 10:carry &lt;&lt; 1;

只要有未包含在总和中的进位,while 循环就会继续。

【讨论】:

  • 感谢您的回答...您能想到 while 循环将运行 3 次或更多次的任何情况吗?我猜carry总是会被结算1到2次..
  • @Sunny 如果您将 1 添加到 0xFFFFFFFF(又名 -1),它将以 32 步(或足够接近 32 的某个数字,我经常使-一个错误)
  • 哦,谢谢。。我现在脑子里很清楚了。现在我可以满意地转移到复杂的部分了。..:)
  • 二进制中的 7 = 00101 怎么算?不应该是00111
  • @Tobber 酷.. 但我还是不明白。如果我们仅以 6 + 7 为例。在按位运算中,6 & 7 = 6 那么进位变量如何初始化为 6?
【解决方案2】:

变量bcarry 用于“携带”额外的数字。例如,在二进制中,1+1 = 10,但10 是两位数。 10 中的 1 必须放在左边的下一位。这就是while() 循环在您的程序中所做的。只要在同一位置有1 数字(a &amp; b),carry 就设置为b 的异或(a ^ b)。仅当 ab(但不是两者)的值为 2 时,这给每个数字的值 1。(在进行二进制算术时,这正是发生的情况;1+1 = 10,所以由于两个 @ 987654337@s 被加在一起,那个地方是0)。之后,carry &lt;&lt; 1carry*2carry 左移一位)分配给b。然后循环重复使用ab 的新值,直到b 为零(这意味着carry 也为零)。

【讨论】:

    【解决方案3】:

    一定要把这里的所有数字都考虑成二进制。

    您通常希望在此类代码中找到“循环不变量”。在这种情况下,您希望看到 a + b 在每次迭代后都保持不变。因此,如果 b 变为 0 并且我们离开循环,则 a 必须等于原始 a 和 b 的总和。下一步是确保循环最终终止。我们稍后会回到这个,首先让我们找出不变的部分,在这种情况下使用相等:

    a + b = (a ^ b) + 2 * (a & b)
    

    在循环中,新 a 将等于旧 a ^ b,新 b 将是 2 * (a & b),这与 (a & b)

    我将介绍两种解决方案。 在这两种情况下,我都会使用一个简单的事实:

    x + y = x ^ y
    

    只要 x 和 y 没有设置公共位。

    正式看到这一点的简短方法是注意:

    a + b = a + b - 2(a & b) + 2(a & b)
    = (a - (a & b)) + (b - (a & b)) + 2(a & b)
    = (a - (a & b)) ^ (b - (a & b)) + 2(a & b)
    = (a ^ (a & b)) ^ (b ^ (a & b)) + 2(a & b)
    = a ^ b + 2(a & b)
    

    冗长的解决方案使用如下数学归纳法(某些人可能认为这有点矫枉过正,但在我看来,这是值得了解的):

    首先确保它在 a 和 b 都等于 0 的情况下工作(您也可以尝试使用一位数来解释该算法的工作原理)。使用数学归纳法时千万不要忘记这一步。

    接下来,假设这适用于 n-1 位数字,我们必须证明它也适用于 n 位数字。现在写 a = 2a' + a'' = 2a' ^ a'' 和 b = 2b' + b'' = 2b' ^ b'' 其中 a'', b'' 在集合 {0, 1} 中(那么 2a' 和 a'' 没有设置公共位!)。 那么:

    (a ^ b) + 2(a & b) = (2a' ^ a'' ^ 2b' ^ b'') + 2((2a'' ^ a') & (2b'' ^ b')) =
    (2a' ^ 2b') ^ (a'' ^ b'') + 2((2a'' & 2b'') ^ (a'' & b'')) = 
    (2a' ^ 2b') + (a'' ^ b'') + 2((2a'' & 2b'') + (a'' & b'')) =
    (2a' ^ 2b') + 2((2a'' & 2b'') + (a'' ^ b'') + (a'' & b'')) =
    2a' + 2b' + a'' + b'' = a + b
    

    现在最后要检查的是这个循环是否真的终止了。 要看到这一点,请使用以下事实:在每个步骤中,a 和 b 都是非负数,并且在每次迭代后仍然如此。

    因此我们得到了 b n >= 2*n > 2**log_2(a+ b) = a+b 与假设相矛盾。 这里的**当然是取幂。

    在 Java 中,此算法也适用于负整数。这是因为负整数在 Java 和大多数编程语言中的存储方式 在这里,有符号数和无符号数的加法和减法在位上的工作方式相同,因此适用于无符号数的代码也适用于有符号数。

    【讨论】:

      【解决方案4】:

      它依赖于 1+1=10(按位)这一事实,即“如果加法意味着进位,则 sum current 的数字列必须为零”。将“

      这是对代码的简单描述。

      carry = Ignore those bits that will produce carry bits (because their sum in the current "column"  will be 0), but collect the carry bits. 
      a = Add those bits of a and b that won't produce a carry bit (i.e. use XOR)
      b = Carry the carry bits one column left.
      

      【讨论】:

        【解决方案5】:

        是的,就像这里的许多答案一样,它就像小学数学一样,可以将两个数字相加。但以二进制方式。

        1. a&b 给了我们什么?它在所有地方都提供了会触发结转的地方。例如,添加 1101 和 0101,只有 右触发器的第 0 和第 2 位置向前移动。所以只有这样 将是 a&b 获得的 0101。但是在正常加法中进行进位时,我们会移动 还剩一个位置。这就是为什么在第三条代码语句中,进位移动了一位 左边的位置。所以进位变成了01010。

        2. a^b 给了我们什么?上面未计算的任何位都包括在内。在上述步骤中,我们包括了第 0 位和第 2 位的影响。 现在我们需要包含其他具有 1 的位。这将是第 3 位 权利给予是1000。分配给a的值变成1000。

        现在我们要做的就是使用上述相同的步骤再次添加它们。

        1. 01010和01000相加。只有共同的一个是第3位,所以a&b给出01000,移位后进位最终变成10000。

        2. 要计算剩余位,a^b 变为 00010 并分配给 a。

        循环继续。因为上面的进位还不是零。

        1. 添加 10000 和 00010。没有共同的 1 位。所以进位变成了0。

        2. a^b 变为 10010 并分配给 a。

        3. 由于进位为零,a 中的任何内容,即 10010 都将成为答案!

        通过例子,更小的例子更好地理解。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-11-20
          • 2014-04-20
          • 1970-01-01
          • 1970-01-01
          • 2023-04-06
          • 2019-10-10
          • 2017-02-17
          • 1970-01-01
          相关资源
          最近更新 更多