概念
- 原码:如果机器字长为n,那么一个数的原码就是用一个n位的二进制数,其中最高位为符号位:正数为0,负数为1,剩下的n-1位表示概数的绝对值。位数不够的用0补全。
- 反码:反码就是在原码的基础上,符号位不变其他位按位取反。
- 补码:补码在反码的基础上按照正常的加法运算加1。
- 移码:移码不管正负数,将其补码的符号位取反。
总结:
- 正数的反码和补码都与原码相同。
- 负数的反码为对该数的原码除符号位外各位取反。
- 负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1
各自的优缺点:
- 原码最好理解,但是加减法不够方便,有两个零
- 反码解决了加减法的问题,还是有两个零
- 补码理解困难,但运算和表示非常方便。
数轴表示:
理论理解起来还是不够直观,用一条数轴观察一个八位机器的数值表示:
-128 -127 ···· -1 0 1 ···· +127
原码: -- 1111 1111 ···· 1000 0001 0000 0000 0000 0001 ···· 0111 1111
1000 0000
反码: -- 1000 0000 ···· 1111 1110 0000 0000 0000 0001 ···· 0111 1111
1111 11111
补码:1000 0000 1000 0001 ···· 1111 1111 0000 0000 0000 0001 ···· 0111 1111
从上面的数轴可以看出用补码表示数值非常方便,形式非常简洁:最高位为符号位,低位从小到大排列,只有一个0,还能多表示一个数。
除此之外:使用补码,可以将符号位和数值域统一处理;加法和减法也可以统一处理;补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。因此在计算机系统中,数值一律用补码来表示和存储。
C语言验证:
#include <iostream>
#include <cstdio>
#define INT_MIN 0x80000000
#define INT_MAX 0x7fffffff
using namespace std;
int main()
{
//32位机器最大整数,输出为:2147483647
cout<<"32位机器整数范围"<<endl;
cout<<(int)INT_MIN<<" -- "<<INT_MAX<<endl;
cout<<endl;
//超过int表示范围cout默认unsigned int类型输出:2147483648
cout<<"溢出后(C++默认转换成无符号输出):"<<endl;
cout<<"0x80000000:"<<0x80000000<<endl;
//超过int表示范围cout默认unsigned int类型输出:2147483649
cout<<"0x80000001:"<<0x80000001<<endl;
cout<<endl;
//指定输出整型,输出:-2147483648
printf("C直接输出溢出结果:\n0x7fffffff:%d\n",0x7fffffff);
printf("0x80000000:%d\n",0x80000000);//指定输出整型,输出:-2147483648
//函数参数为十六进制表示的补码,输出补码对应的原码的数值为:-2147483647
printf("0x80000001:%d\n",0x80000001);
//函数参数为十六进制表示的补码,输出补码对应的原码的数值为:-1
printf("最大负整数:0xffffffff:%d\n",0xffffffff);
int b=0x0001;
//~b是对b的补码(包括符号位)按位取反,~b,0xfffe,此补码对应的值为-2,补码0xffff对应-1
printf("\n0x0001:%d\n~0x0001:%d\n",b, ~b);//输出为-2
return 0;
}
运行结果:
参考链接: 原码、反码、补码和移码其实很简单