[英]Add unsigned numbers without using '+' or '++'
我需要添加2个无符号数字'a'和'b'。
我使用位操作找到了以下代码
unsigned int add (unsigned int a,unsigned int b)
{
unsigned int carry, sum;
if (b == 0)
{
return a;
}
sum = a ^ b; // xor takes sum
carry = a & b; // collect carry;
carry = carry << 1;
return ( add (sum, carry) );
}
我无法弄清楚这段代码是如何添加两个数字的。
任何帮助/指导的人。
逻辑 :代码实现了一系列半加法器,并通过递归将进位从其中一个传递到下一个。 有关其工作原理的示例,请参阅“干运行”。
考虑这两个值a=0011
和b=0101
。 在基数10中,它们分别是a=3
和b=5
。
现在, a^b=0110
( 1
仅当单个位为1
),而a&b=0001
( 1
仅当两个位为一,则单一的情况下可以有一个进位)。
然后,你需要将进位移动到下一位,这就是为什么你有<<1
操作,使carry=0010
。
现在需要使用上述算法添加0110
和0010
。 这将变成添加0100
和0100
。 这将导致添加0000
与1000
,这将导致添加1000
与0000
,其将通过基本情况( b == 0
)结束。
以表格形式:
| a | b | a^b | a&b | carry|
------------------------------------
| 0011 | 0101 | 0110 | 0001 | 0010 |
| 0110 | 0010 | 0100 | 0010 | 0100 |
| 0100 | 0100 | 0000 | 0100 | 1000 |
| 0000 | 1000 | 1000 | 0000 | 0000 |
| 1000 | 0000 | ---- | ---- | ---- |
最后一行是基本情况。
记住这一点:
2003 Standard C ++ 5.3.1c7:
通过从2 ^ n减去其值来计算无符号数量的负数,其中n是提升的操作数中的位数。
a+b = a - (-b)
将在符合2003标准(顺便提一下C ++ 11)的C ++编译器中工作。
当然,我会提出一个符合早期标准的答案(C ++ 99,例如-unsigned
未定义)。
问题中的位操作代码使用两个基本原则: 半加法器和加法是可交换的这一事实。
单半加法器增加两位,没有进位。 如果输入中的一个输入为1,则单个位添加结果为1,如果输入相等则为0。 这由代码中的按位xor
表示。
即使这样做,你也需要处理这些行为。 如果两个位都是1,则从位位置执行,否则为0。 这通过按位and
后续shift
的组合来表示,以将进位移动到需要添加的位位置。
对add的递归调用使用添加是可交换的事实来应用进位。 无论是随着初始添加逐位添加进位,还是在后续步骤中进行批量添加都无关紧要。
添加进位可能会导致新的结转。 这是通过继续递归调用来处理的,直到添加没有进位。
必须达到递归基本情况,零进位,因为加零,零进位,不能导致进位。 如果进位的最低有效k
位在一次进位加法上全为零,则下一进位的至少k+1
最低有效位必须为零。
要理解函数实际上为什么添加两个数字,有必要查看真值表以增加两位:
a = 0,b = 0 - > a + b = 00
a = 0,b = 1 - > a + b = 01
a = 1,b = 0 - > a + b = 01
a = 1,b = 1 - > a + b = 10
您会看到较低位是两个输入位的XOR,较高位是两个输入位的AND,因此最终结果由(a XOR b)OR((a AND B)<< 1)表示。 由于此函数添加了32位数字,因此不能简单地对结果进行OR运算,因为在组合XOR和AND运算的结果时,一些额外的进位可以出现在高位数中,这就是为什么必须递归应用该函数的原因。
顺便说一句,这几乎就是在硬件中添加数字的方式。
这是硬件实现添加的方式。 在位上添加的结果是位的排除或(C ++中的^
运算符); 这就是你得到的sum
。 但这并未考虑来自较低位的任何进位。 进位输出是比特(的和&
运营商),它给你的初始值carry
。 但是位n的进位是位n + 1的进位,所以我们向左移动,将位n移动到位n + 1,并将其加入。
我们使用递归来添加它,因为如果在添加进位之前的结果(在位级别)是1,并且进位是1,则还将有执行。
递归结束的时候有点微妙(当然,硬件版本不会递归,而是添加额外的逻辑)。 通过考虑原始值,最容易评估:
a b carry_in sum carry_out
0 0 0 0 0
1 0 0 1 0
0 1 0 1 0
1 1 0 0 1
0 0 1 0 0
1 0 1 1 1
0 1 1 1 1
1 1 1 0 1
(“sum”列是a ^ b
的结果,没有进位。)
在第一次递归调用时,b的第0位将为0(因为它表示低阶位的carry_in,并且没有一个 - 或者因为<<
,它将位n的进位输出移位到位的carry_in n + 1)。 当然,位0的carry_in将为0.因此,对于第一次递归调用中的位0,只能出现前两行。 这意味着不存在carry_out,并且对于下一次递归,只有前两行与位0和1相关。换句话说,每次递归都有效地从传播的进位中消除了一个位集,结果是传播了进位必须最终变为0.并且由于它作为参数b传播,参数b最终必须变为0。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.