首先是整数:
计算机中所有的整数都是以补吗的形式存储的。整数的补吗就是它本身,负数的补码是它除了符号位的位数取反加一。
使用补码的原因:
简化计算机基本运算电路,使加减法都只需要用加法电路实现,用加法替代减法。
正数的补码等于原码;负数的补码等于反码加1,而反码等于原码符号位不变,其余各位取反
代表的意义
目的:为了简化计算机基本运算电路,使加减法都只需要通过加法电路实现,也就是让减去一个正数或加上一个负数这样的运算可以用加上一个正数来代替。于是改变负数存储的形式,存储成一种可以直接当成正数来相加的形式,这种形式就是补码。(正数不用变,所以接下来的讨论中一般略去正数)
补码是怎么把减法变成加法的?
用时钟理解减法变加法
这是一个身边的例子,当你校对时钟的时候,假设发现钟是6点,但实际上现在才2点,也就是它走快了4个小时,你可以有两种方法进行校正,一种是逆时针拨回4个小时到2点,另一种是顺时针拨6个小时到12点然后再拨2小时,也就是顺时针拨8个小时到2点。所以对于时钟的表盘来说,设-N表示逆时针拨N个小时,N表示顺时针拨动N个小时,那么-4 = +8,同样还会有 -1 = +11、-5 = +7,甚至也可以 -4 = +8 = +20 = +32 = -16…
这里边隐藏了什么规律?其实在数学中,-4、+8、+20、+32、-16可以归为符合某个条件的同一类数字 —— 对于模12同余。(这不是反码吗?)
所以,只要 补码 是一个负数的正同余数,那么就能实现加这个正同余补码跟加另一个负数是一样结果的效果。对一个负数来说,有无数个正同余数满足条件,为了减少不必要的运算,可以规定补码就取其中最小的正数。
可能因为通过原码求 补码 是一个补模运算,所以它被称为 补码 。
两个用补 码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。
探究符号位在运算中时如何被改变的:
定义一个int类型的数,有四个字节。一个字节8个位。最大的负数:
int a= 0xFFFFFFFF;
FF FF FF FF这是所有位都是1,我想把他的最高位置0。
Int b=~(1<<31)&a;
在使b加一:
int a=0xFFFFFFFF;
int b = ~(1 << 31)&a;
b = b + 1;
cout << "sizeof(a)=" << sizeof(a) << " a=" << a << endl;
cout << "sizeof(b)=" << sizeof(b) << " b=" << b << endl;
输出结果:
二进制1111等于十六进制F。二进制0111等于十六进制7那么:
b,的第32位(符号位)为0,其他为1.,加一后导致进位到符号位。c,与d与此情况相同,符号位确实是被进位了。
为什么补码可以消除负0?
负0的二进制原码(假设是4位):1000,那么它的补吗是:
取反:1111,加1:0000。与正0(二进制0000)相同。就是产生了一个进位,但是这个进位溢出后被丢弃了。
再有就是浮点数的表示法:
浮点数的表示:
举例来说:
十进制的5.0,写成二进制是101.0,相当于1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。
IEEE 754对有效数字M和指数E,还有一些特别规定。
如:1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
E为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127(2^7-1);对于11(2^10-1)位的E,这个中间数是1023。
比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
然后,指数E还可以再分成三种情况:
(1)E不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
(2)E全为0。这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。E为0
(3)E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。
这里实际上的公式是这样:
浮点数=(-1)^s* 2^E * 1.f
在存储的时候是这样:
1位 |
8位 |
23位 |
s[31] |
E[23-30] |
f[0-22] |
E表示了小数点在数字中的位置,由e转化而来,公式为:E=e+127
F数位不够则向右补0.
浮点数精度问题是怎么产生的?
目前C/C++编译器标准都是按照IEEE制定的浮点数表示法来进行float,double运算的。
就是按照下面这张图来的32位是float的表示,64位是double的表示:
例如一个double类型的数38414.4
将整数部分二进制化(十六进制化):960E
而小数部分:
0.4=0.5*0+0.25*1+0.125*1+ … …
这个是永远也写不完的,这就是著名的浮点数精度的问题。
十进制小数转二进制:
“乘2取整,顺序排列”
0.4 *2=0.8 0 取个位数0为二进制第一位。0.8继续计算。
0.8*2=1.6 1取个位数1为二进制第一位。0.6继续计算。
0.6*2=1.2 1
0.2*2=0.4 0
0.4*2=0.8 0
……
这样就产生了无限循环,是不可能表示完全的。