部分相关内容引用博客:http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html
原码、反码和补码的概念
1.机器数
一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机中用一个数的最高位存放符号,正数为0,负数为1。比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是10000011。这里的00000011和10000011就是机器数。
2.真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
3.原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值。 比如如果是8位二进制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
[1111 1111 , 0111 1111]
也就是[-128,127],其中1111 1111表示-127,1000 0000表示-128,而0000 0000表示0,0111 1111表示127。
反码
反码的表示方法是:正数的反码是其本身,负数的反码是其原码的基础上,符号位不变,其余各位取反。
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值,通常要将其转换成原码再计算。
补码
补码的表示方法:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后加1(也就是在反码的基础上加1)
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
对于负数,补码表示方式也是人脑无法直观看出其数值的。通常也需要转换成原码在计算其数值。
数值在计算机系统中存储形式
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理,同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
模的概念
模的概念可以帮助理解补数和补码。“模”是指一个计量系统的计数范围。计算机也可以看成一个计量机器,它也有一个计量范围,即都存在一个“模”。
比如时钟:
时钟的计量范围是0~11,模=12
表示n位的计算机计量范围是0~2^(n)-1,模=2^(n)
“模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。任何有模的计量器,均可化减法为加法运算。
例如:假设当前时针指向10点,而准确时间是6点,调整时间可有以下两种拨法:
1.倒拨4小时:10-4=6;
2.顺拨8小时:10+8=(12+6) mod 12=6;
在以12模的系统中,加8和减4效果是一样的,因此凡是减4运算,都可以用加8来代替。对“模”而言,8和4互为补数。实际上以12模的系统中,11和1,10和2,9和3,7和5,6和6都有这个特性。共同的特点是两者相加等于模。
对于计算机,其概念和方法完全一样。n位计算机,设n=8,所能表示的最大数是1111 1111,若再加1成为1 0000 0000(9位),但因只有8位,最高位1自然丢失。又回了00000000,所以8位二进制系统的模为2^8。在这样的系统中减法问题也可以化成加法问题,只需把减数用相应的补数表示就可以了。把补数用到计算机对数的处理上,就是补码。
因此:钟表往回拨(减法)的结果可以用往前拨(加法)替代。现在的焦点就落在了如何用一个正数, 来替代一个负数。
同余的概念
两个整数a,b,若它们除以整数m所得的余数相等,则称a,b对于模m同余:
记作 a ≡ b (mod m) 读作a与b关于模m同余。
比如:
4 mod 12 = 4
16 mod 12 = 4
28 mod 12 = 4
表示4, 16, 28关于模12同余。
负数取模
正数取模很简单,但是负数相对麻烦一点:
x mod y = x - y[x/y]
其中“[ ]”表示取下界符号,上面公式的意思是:x mod y等于x减去y乘上x与y的商的下界.
以 -3 mod 2 举例:
-3 mod 2
= -3 - 2x[-3/2]
= -3 - 2x[-1.5]
= -3 - 2x(-2)
= -3 + 4 = 1
时钟上的同余
再回到时钟的问题上:
回拨4小时 = 前拨8小时
(-4) mod 12 = 8
8 mod 12 = 8
表示-4与8是同余的。-4和8在模的区间内表示的是同一位置。同理对于:
4 mod 12 = 4
16 mod 12 = 4
28 mod 12 = 4
4,16和28对于模为12的区间内都是表示同一个位置4。
同余数的两个定理
反身性:
a ≡ a (mod m)
线性运算定理:
如果a ≡ b (mod m),c ≡ d (mod m) 那么,
(1).a ± c ≡ b ± d (mod m)
(2).a * c ≡ b * d (mod m)
比如:
7 ≡ 7 (mod 12)
(-2) ≡ 10 (mod 12)
现在我们要计算式子:7-2,式子中有一个负数-2, 我们找到了它的正数同余数10来替代它,也就是说-2和10在模12区间内代表的是同一个位置:
因此有:7-2=(7+10) mod 12 =5 这就把减运算转化为加运算。
由于 7 -2 ≡ 7 + 10 (mod 12) 成立,其实5和17在模12区间内也是代表的是同一个位置。
在计算机中的减运算过程
例如:为了简单起见,我们设定计算机的尾数为4,其中最高位为符号位,表示范围是[1111 0111],也就是范围[-8,7],1111表示-7,1000表示-8,0000表示,0111表示7。例如求解:6-4。模为8。
+6:[+6] = [0110]原 = [0110]反 = [0110]补
-4:[-4] = [1100]原 = [1011]反 = [1100]补
其中,可以看出-4的补码就是4,经过计算-4的同余数为4。则:
0110 + 1100 = 1 0010
最高位溢出,舍去。则最后的结果0010,因为是补码,必须转换为原码,由于是正数的原码就是补码,因此最终的结果就是0010(2)。
有例如计算:(-1) + (-7)
-1:[-1] = [1001]原 = [1110]反 = [1111]补
-7:[-7] = [1111]原 = [1000]反 = [1001]补
1111 + 1001 = 1 1000
最高位溢出,舍去。则最后的结果1000,因为是补码,必须转换为原码,由于是正数的原码就是补码,000减1得111,然后每一位取反的000,最后结果为1000(-8)。
目前计算机一般是32位,元素过程是一样。
所以说一个数的补码, 实际上是这个数对于一个膜的同余数. 这就和钟表一样, 转了一圈后总能找到在可表示范围内的一个正确的数值!
unsigned int和int的区别
拿单字节整数来说,无符号型,其表示范围是[0,255],总共表示了256个数据。先看无符号,0表示为0000 0000,255表示为1111 1111,刚好满足了要求,可以表示256个数据。
有符号型,其表示范围是[-128,127]。对正数来说,127是0111 1111,1是0000 0001,0是0000 0000。负数呢,-1是1000 0001,1111 1111表示的数据是-127。1000 0000表示-128。
#include <iostream>
#include <stdio.h>
using namespace std;
int main(){
unsigned int a=1; //无符号整型
int b=-3; //有符号整型
cout<<sizeof(a)<<" "<<sizeof(b)<<endl; //输出无符号整型变量和有符号整型变量的占用空间大小
int c;
c=(a>b); //无符号和有符号做运算 都统一按无符号做运算
cout<<a+b<<endl; //无符号和有符号做运算 都统一按无符号做运算 因此cout自动按无符号输出
printf("%d\n",a+b); //print函数中%d是按有符号输出
printf("%u\n",a+b); //printf函数中%u是按无符号输出
cout<<c<<endl; //输出结果为0
system("pause");
}
程序输出结果:
程序分析:
变量a和变量b的大小为4个字节,一共32位:
+1原码: 0000 …. 0001
-3原码: 1000 …. 0011
-3反码: 1111 …. 1100
-3补码: 1111 …. 1101
相加的: 1111 …. 1110
也就是:2^(32)-1-1=4294967294
也可以这样解释:
-3+1=-2
-2补码: 1111 …. 1110 也就是2^(32)-1-1=4294967294。
注意:float和double总是带符号的。
#include <iostream>
#include <stdio.h>
using namespace std;
int main(){
unsigned int a; //无符号整型
int b=-1; //有符号整型
a=b; //用有符号整型赋值给无符号整型
cout<<a<<endl;
unsigned int x=0xffffffff; //无符号整型
int y; //有符号整型
y=x; //用无符号整型赋值给有符号整型
cout<<y<<endl;
unsigned char n=254; //1111 1110
char m;
m=n; //m的存储的二进制位1111 1110 该数据是补码 转换原码为1000 0010 也就是2
printf("%d\n",m);
system("pause");
}
程序输出结果为: