【转载】你真的会浮点数与整型数的"互转"吗?

看了标题,你是不是觉得这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;

  1. S: 符号位(Sign) : (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数;
  2. E: 指数位(Exponent): M表示有效数字,大于等于1,小于2;
  3. M: 尾数部分(Mantissa): 2^E表示指数位.

指数E还可以再分成三种情况:

  1. E不全为0或不全为1.这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1.
  2. E全为0.这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数.这样做是为了表示±0,以及接近于0的很小的数字.
  3. 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程序员而言,这些东西,确实是彩笔才写的问题.但是,像我这种半路出家的人来说,路还很远!



作者:Ljson
链接:https://www.jianshu.com/p/810f89d97421
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2018-04-22 16:50  上清风  阅读(316)  评论(0编辑  收藏  举报