为什么这个用于添加的代码(使用按位运算)在java中有效

时间:2013-06-27 11:34:31

标签: java bit-manipulation bitwise-operators

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 但这是为了乘法,而不是为了加法。

5 个答案:

答案 0 :(得分:15)

小学首次回忆新成员。例如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,您得到

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

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

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

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

答案 1 :(得分:1)

变量bcarry用于“携带”额外数字。例如,在二进制中,1+1 = 10,但10是一个两位数字。 1中的10必须放在左侧的下一个数字中。这就是while()循环在程序中的作用。如果同一位置(1)中有a & b个数字,则carry设置为ba ^ b)的异或。只有当1a(而不是两者)的值都为2时,才会为每个数字赋予b的值。(在进行二进制算术时,确实会发生这种情况; {{ 1}},因为两个1+1 = 10被添加在一起,所以那些地方是1)。之后,0carry << 1carry*2向左移动一次)被分配给carry。然后使用ba的新值重复循环,直到b为零(这意味着b也为零)。

答案 2 :(得分:1)

肯定会想到二进制系统中的所有数字。

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

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

其中在循环中新a将等于old a ^ b并且new b将是2 *(a&amp; b),其与(a&amp; b)&lt;&lt;这是你的问题的本质 - 弄清楚这种平等。这正是你做加法的方式。

我将提出两种解决方案。 在这两个方面,我将使用一个简单的事实:

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都等于零(你也可以尝试一个比特数来解释这个算法的工作原理)。使用数学归纳时,不要忘记这一步。

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

(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&lt; = a + b。 接下来请注意,在n个步骤之后,b必须以n个零结束。因此,我们不能做多于log_2(a + b)步骤,因为我们得到b = 0或b = k * 2 * n&gt; = 2 * n&gt; 2 ** log_2(a + b)= a + b矛盾的假设。 这里**表示当然是取幂。

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

答案 3 :(得分:0)

它依赖于1 + 1 = 10(按位)的事实,即“如果加法意味着进位,则和电流的数字列必须为零”。想想“&lt;&lt;”运算符“向左移位”而不是“将int乘以2”

这是对代码的平凡描述。

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.

答案 4 :(得分:0)

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

  
      
  1. &amp; b给我们带来了什么?它会在所有地方提供会触发结转的地方。例如,添加1101和0101,只有   右触发的第0和第2位继续前进。只有那样   将是由&amp; b获得的0101。但是当正常添加时,我们移动   剩下一个位置。这就是为什么在第3个代码语句中,进位被移动了一个   位置左。因此,随身携带成为01010。

  2.   
  3. ^ b给我们带来了什么?包括任何未计入的位数。在上面的步骤中,我们包括第0和第2位的影响。   现在我们需要包含其他位数为1.这将是第3位   right give是1000.分配给a的值变为1000。

  4.         

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

         
        
    1. 添加01010和01000.只有普通的一个是第三个位置,所以a&amp; b给出01000并且在转换后最终变为10000.

    2.   
    3. 要计算剩余的比特,a ^ b变为00010并被分配给a。

    4.         

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

           
          
      1. 添加10000和00010.没有共同的1位。因此,随身携带变为0。

      2.   
      3. a ^ b变为10010并被分配到。

      4.   
      5. 由于随身携带是零,无论是什么,10010就成了答案!

      6.   

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