public int add(int a, int b){
while (b != 0){
int carry = (a & b) ;
a = a ^ b;
b = carry << 1;
}
return a;
}
这是使用按位运算计算两个整数之和的代码。
如果我手动/编程计算,我看它适用于每个整数。
但我无法弄清楚a
和carry
的中间值之间的任何关系。
为什么进位乘以2来分配给b
?
答案 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,您得到
首先你找到了携带物;这是a
和b
都设置了位的所有位置:int carry = (a & b) ;
然后id会添加数字,忽略进位,并将其存储在a
中:a = a ^ b;
这将在示例中响应6+7=3
。
最后一部分将进位移动到下一个数字位置,即确保示例中的1进位从1'移动到10':carry << 1;
只要存在未包含在总和中的进位,while循环就会继续。
答案 1 :(得分:1)
变量b
和carry
用于“携带”额外数字。例如,在二进制中,1+1 = 10
,但10
是一个两位数字。 1
中的10
必须放在左侧的下一个数字中。这就是while()
循环在程序中的作用。如果同一位置(1
)中有a & b
个数字,则carry
设置为b
(a ^ b
)的异或。只有当1
或a
(而不是两者)的值都为2时,才会为每个数字赋予b
的值。(在进行二进制算术时,确实会发生这种情况; {{ 1}},因为两个1+1 = 10
被添加在一起,所以那些地方是1
)。之后,0
(carry << 1
或carry*2
向左移动一次)被分配给carry
。然后使用b
和a
的新值重复循环,直到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)
是的,就像这里的许多答案一样,它可以像基本的学校数学一样添加两个数字。但是以二进制的方式。
&amp; b给我们带来了什么?它会在所有地方提供会触发结转的地方。例如,添加1101和0101,只有 右触发的第0和第2位继续前进。只有那样 将是由&amp; b获得的0101。但是当正常添加时,我们移动 剩下一个位置。这就是为什么在第3个代码语句中,进位被移动了一个 位置左。因此,随身携带成为01010。
- 醇>
^ b给我们带来了什么?包括任何未计入的位数。在上面的步骤中,我们包括第0和第2位的影响。 现在我们需要包含其他位数为1.这将是第3位 right give是1000.分配给a的值变为1000。
现在我们要做的就是使用上面相同的步骤再次添加它们。
添加01010和01000.只有普通的一个是第三个位置,所以a&amp; b给出01000并且在转换后最终变为10000.
- 醇>
要计算剩余的比特,a ^ b变为00010并被分配给a。
循环继续。因为上面的进位还不是零。
添加10000和00010.没有共同的1位。因此,随身携带变为0。
a ^ b变为10010并被分配到。
- 醇>
由于随身携带是零,无论是什么,10010就成了答案!
通过示例,更小的示例更好地理解。