一、题目描述
设计一个函数把两个数字相加。不得使用 + 或者其他算术运算符。
示例:
输入: a = 1, b = 1 输出: 2(不用加法)
a、b均可能为负数
二、解题思路
加法运算不能使用+,那么可以考虑使用二进制来实现。
1、二进制相关操作符
用于整数之间的二进制表示进行的操作。一般是32位来表示,补码表示下进行,正数:原反补相同,负数:补码=原码取反(反码)+1。最高位作为符号位,0表示正数,1表示负数。默认是有符号的。需要注意的是,在C语言中对于无符号整数(unsigned int、unsigned short等)的操作不是在补码表示下进行的,而是直接按照原码进行操作。因为无符号整数没有符号位,所以不涉及符号位扩展和补码运算。在进行运算时,不需要手动补全操作数,会自动按位操作两个操作数的对应位。
(1)、按位与(&):两个对应位都是1,则结果位为1,否则为0,对两个二进制数的每一个对应位执行与操作。
00000101
& 00001001
----------------
00000001
#include <stdio.h>
int main()
{
int num1 = 0b1011; // 二进制数00000000 00000000 00000000 00001011
int num2 = 0b1001; // 二进制数00000000 00000000 00000000 00001001
int a= num1 & num2; // 按位与操作
int num3 = 5;// 二进制数00000000 00000000 00000000 00000101
int num4 = 6;// 二进制数00000000 00000000 00000000 00000110
int b= num3 & num4;
int num5 = -5;
//5的二进制表示:00000000 00000000 00000000 00000101(原码)
//取反得到反码: 11111111 11111111 11111111 11111010
//反码加1得到补码:11111111 11111111 11111111 11111011
int num6 = -6;// 二进制数11111111 11111111 11111111 11111010
int c= num5 & num6;
printf("结果为十进制:%d\n", a);//结果为十进制:9 二进制:00000000 00000000 00000000 00001001
printf("结果为十进制:%d\n", b);//结果为十进制:4 二进制:00000000 00000000 00000000 00000100
printf("结果为十进制:%d\n", c);//结果为十进制:-6 二进制:11111111 11111111 11111111 11111010
return 0;
}
(2)、 按位或(|):两个对应位如果有一个为1,则结果位为1,否则为0。
00000101
| 00001001
----------------
00001101
#include <stdio.h>
//8位二进制表示
int main() {
int num1 = 5; // 二进制表示为 00000101
int num2 = 9; // 二进制表示为 00001001
int result = num1 | num2; // 按位或操作
printf("结果为十进制:%d\n", result);//结果为二进制:13 二进制:00001101
return 0;
}
(3)、按位异或(^):如果两个对应位不同则结果为1,相同则结果为0。
00000101
^ 00001001
----------------
00001100
#include <stdio.h>
int main() {
int num1 = 5; // 二进制表示为 00000101
int num2 = 9; // 二进制表示为 00001001
int result = num1 ^ num2; // 按位异或操作
printf("结果为十进制:%d\n", result);//结果为十进制:12 二进制:00001100
return 0;
}
(4)、按位取反(~):对二进制数的每一位执行取反操作,0变为1,1变为0。
#include <stdio.h>
int main() {
unsigned int num = 5; // 二进制表示为 00000000 00000000 00000000 00000101
unsigned int result = ~num; // 按位取反操作
printf("结果为:%u\n", result);//结果为:4294967290 // 二进制:11111111 11111111 11111111 11111010
int num1 = 5; // 二进制表示为 00000000 00000000 00000000 00000101
int result1 = ~num; // 按位取反操作
printf("结果为:%d\n", result1);//结果为:-6// 二进制:11111111 11111111 11111111 11111010
return 0;
}
(5)、左移(<<):将一个二进制数的所有位向左移动指定的位数,右侧空出的位用0填充。
无符号位:左移操作会将二进制数向左移动指定的位数,右侧空出的位用0填充。无符号整数没有符号位的概念,因此左移操作只是简单地将所有位左移,不会导致符号位改变。
00000000 00000000 00000000 00000101(5)
<<29
---------------------------------------
10100000 00000000 00000000 00000000 (结果为2684354560)
有符号位:左移操作会将二进制数向左移动指定的位数,右侧空出的位用0填充。但如果左移操作超过了整数类型的位数,结果是未定义的行为。如果左移导致符号位的改变,则结果是未定义的行为。左移操作可能导致溢出或者符号位的改变。
对于32位整数类型(如int),最大值和最小值如下:
最大值:2147483647(十进制)或 01111111 11111111 11111111 11111111(二进制)
最小值:-2147483648(十进制)或 10000000 00000000 00000000 00000000(二进制)
当对一个带符号位的整数进行左移操作时,左移的位数超过了整数类型的位数(32位),就会导致左移超出范围。例如,对于一个32位整数类型,左移超过31位就会超出范围。这时,左移操作的结果就是未定义的,可能会导致符号位的改变或溢出。
不论正负,补码移动,移动之后再转原码输出。
#include <stdio.h>
int main() {
int num = -5; // 二进制表示为 11111111 11111111 11111111 11111011
int result = num << 5;
printf("结果为十进制:%d\n",result);//结果为十进制:-160
//补码 11111111 11111111 11111111 0110000
//反码 11111111 11111111 11111111 0101111
//原码 10000000 0000000 0000000 1010000
return 0;
}
(6)、右移(>>):将一个二进制数的所有位向右移动指定的位数,左侧空出的位根据符号位或补码规则进行填充。
无符号位:右移之后,高位补0。
00000000 00000000 00000000 00000101(5)
>>2
---------------------------------------
00000000 00000000 00000000 00000001 (1)
有符号位:高位按照符号位补全。
正数:符号位为0,符号位不动,高位补0
00000000 00000000 00000000 00000101(5)
>>2
---------------------------------------
00000000 00000000 00000000 00000001 (1)
负数:符号位为1,符号位不动,高位补1
11111111 11111111 11111111 11111011(-5原码转补码移动)
>>2
---------------------------------------
11111111 11111111 11111111 11111110 (补码转原码输出)
#include <stdio.h>
int main() {
int num = -5; // 二进制表示为 11111111 11111111 11111111 11111011
int result = num >> 2;
printf("结果为十进制:%d\n",result);//结果为十进制:-2
//补码 11111111 11111111 11111111 11111110
//反码 11111111 11111111 11111111 11111101
//原码 10000000 0000000 0000000 00000010
return 0;
}
二、两个二进制数相加演示
例:8+9:
8的二进制表示为: 1000
9的二进制表示为: 1001
1000 (8的二进制)
+ 1001 (9的二进制)
-------
10001 (结果为17的二进制)
可以发现,两个二进制数对应位相加,0+0->0、1+1->写0进位1、1+0->1
由于要考虑进位,我们把该运算过程分为直接相加部分和进位部分
进位通过&实现:都为1结果为1,否则为0。
直接相加部分通过^实现:相同为0,不同为1。
代码如下:
int add(int a, int b){
while(b!=0) //b为0时代表无进位,输出结果
{
int c=a&b; // 计算进位位
a=a^b; // 执行无进位加法
b=(unsigned int)c<<1; //将进位左移一位
}
return a;
}
在第三步时使用了无符号整型强制类型转换,确保左移操作的结果不受符号位影响。(考虑负数相加情况)