看了标题,你是不是觉得这TM是哪个iOS彩笔写的入门文章.好的,那咱们先来看看几个例题,看看你有没有白白点进来!
int main() {
float a = -6.0;
int *b = &a;
NSLog(@"1=> %d",*b);
*b = -6;
NSLog(@"2=> %f",a);
return 0;
}
请问上面的1、2分别输出什么?(赶紧拿出草稿纸算一算吧!)
什么,这么简单的题目还要拿出草稿纸算,你TM是在侮辱我的智商吗?
输出结果不就是这个吗?
1=> -6
2=> -6.000000
还说不是彩笔写的文章,TM骗流量!
别急着返回,往下看!
首先为了防止你直接走了,那么我先抛出输出结果!看好了,千万别眨眼:
输出结果:
1=> -1061158912
2=> nan
对,没有看错,这就是输出结果.
我知道,有些人还不信,如果你确实不相信,而且你旁边有电脑,代码拷进Xcode跑一下,看看是这结果吗?(跑完之后再回来看,没错,相信我).
好的,跑完代码,你就很想往下看了吧,那就让我们一起来分析下,怎么会有这么魔性的结果,这不科学啊!!!!
首先,问题的关键在于不同类型的指针,指向相通的数据.
那么数据在计算机中是如何保存的呢?当然是二进制啊,就是一堆0和1.但是0和1只有计算机能懂啊,我们程序员哪里知道,一坨0和1表示什么呢!所以,计算机就要将保存在内存当中的0和1转换为我们能看得懂的数据,数字当然就被转换为了10进制的.这里有个关键点: 二进制数转换为十进制数.
这里抽离出了两个关键点 :
** 1. 整型数据 <==> 浮点型数据 **
**2. 进制 <==> 十进制 **
那么数据在内存中具体的保存方式是什么呢?
整型数据 4个字节 32位 每一位都是0或1,首位代表符号位.首位1代表负数;首位0,代表正数;
如: 5 =(转换为2进制)= 0 00..(中间略掉24个0)..00101
但是负数,保存的就是他的补码了.
补码 = 反码 + 1;
比如: -5 的 补码计算方式:
5 =(转为二进制)=> 0 00..(中间略掉24个0)..00101
=(求反码:0变为1,1变为0)=> 1 11..(中间略掉24个1)..11010
=(补码:反码 + 1)=> 1 11..(中间略掉24个1)..11011
好的,比较简单是吧!
那我们再看看浮点型数据的保存方式吧:
首先有一个公式,来将浮点型数据转化为我们好保存的结果,公式如下:
V = (-1)^S * M * 2^E;
- S: 符号位(Sign) : (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数;
- E: 指数位(Exponent): M表示有效数字,大于等于1,小于2;
- M: 尾数部分(Mantissa): 2^E表示指数位.
指数E还可以再分成三种情况:
- E不全为0或不全为1.这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1.
- E全为0.这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数.这样做是为了表示±0,以及接近于0的很小的数字.
- E全为1.这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN).
比如:
6.0 =(二进制)=> 110.0 =(按上式转换)=> (-1)^0 * 1.1 * 2^2 => S = 0;M = 1.1; E = 2;
那么有了公式,又有什么用呢?这个公式其实完全告诉我们浮点数据的保存方式了.
具体是这样的,在32位的二进制数据中,第1位保存S,接下来8未保存E,剩下的保存M.
注意,为了能让E表示负数,在保存的时候,会将它 +127,在取的时候,又捡回去,这样就能表示负数了;由于M都是1.,所以就不保存前面的1了,在取的时候再拿回去.*
说这么多,还是煮个🌰吧,还是上面的6.0:
6.0 =(按公式转换)=> (-1)^0 * 1.1 * 2^2 => S = 0;M = 1.1; E = 2;
那么保存方式就是:
[S(符号1位) E(指数8位: +127之后再保存) M(位数23位:去掉首位的1再保存)]
=>[0 129 1] =>[0 10000001 1000..(中间略掉17个0)..00]
如果把他当整型取出来会是什么结果呢?
那就是:
2^30 + 2^23 + 2^22 = 1086324736;
(可以自己敲代码验证一下,当然也可以笔算)
既然知道了数据在内存中的存储方式了,那不就结了.
有了刀,那就可以开始切菜了!!!
第一个问题就是: 将 -6.0的浮点数当整型数取出来,结果是什么?
首先套公式 :
-6.0 = -110.0 = (-1)^1 * 1.1 * 2 ^2 => S = 1;M = 1.1;E = 2;
那么它在内存中的形式就是:
[S(符号1位) E(指数8位: +127之后再保存) M(位数23位:去掉首位的1再保存)]
=>[1 129 1] =>[1 10000001 1000..(中间略掉17个0)..00]
接着我们将它当做整型数取出来.首先看到,符号位为1,那么是负数.回忆一下负整型数是怎么保存在内存当中的呢?对,保存 补码.补码是反码+1.那我们现在把它发过来做就可以拿到原码了.即-1,然后取反码.
[1 10000001 1000....00]
=(-1)=> [1 10000001 0111..(中间略掉17个1)..11]
=(取反码)=>[0 01111110 1000..(中间略掉17个0)..00]
结果就是:
-(2^29 + 2^28 + 2^27 + 2^26 + 2^25 + 2^24 + 2^22) = -1061158912;
是不是感觉很爽,我他妈竟然把所谓的"bug""的结果算出来了.不要太激动,我们继续,第二个问题:
第二个问题是:把整型的-6当浮点型取出来.
好的,负整型保存补码;
6 =(2进制)=> [00..(中间略掉25个0)..00 110]
=(反码)=> [11..(中间略掉25个1)..11 001]
=(+1)=> [11..(中间略掉25个1)..11 010]
可以看到,他的指数位全部为1.我们再回头看看 指数E的三种情况.E全为1,这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN:not a number).
所以最终的打印结果为 nan
One More Thing
可能对于科班出身的iOS程序员而言,这些东西,确实是彩笔才写的问题.但是,像我这种半路出家的人来说,路还很远!