计算机中的原码、反码和补码

2015/06/14 C和C++基础

部分相关内容引用博客:http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html

原码、反码和补码的概念

1.机器数

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机中用一个数的最高位存放符号,正数为0,负数为1。比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是10000011。这里的0000001110000011就是机器数。

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");
}

程序输出结果为:

Search

    Post Directory