• 【转】float类型在内存中的表示


    http://www.cnblogs.com/onedime/archive/2012/11/19/2778130.html

    http://blog.csdn.net/adream307/article/details/7246993

    http://wenku.baidu.com/link?url=Q_SYeQffEjdS1cpMXIRncmmhwKA_o2978-0ei1_gz9ym2vrmmBrSEZArpE6tR4yCB9PEHLG_FHRakijbr9-Y0DIK_MTjBUTKoXUhgIYETB3

    ZC:

        float f;
        int j = 0x7FFFFFFF;
        memcpy(&f, &i, sizeof(i));

    一、

    浮点型变量在计算机内存中占用4字节(Byte),即32-bit。遵循IEEE-754格式标准。
    一个浮点数由2部分组成:底数m 和 指数e。
                              ±mantissa × 2exponent
    (注意,公式中的mantissa 和 exponent使用二进制表示)
    底数部分 使用2进制数来表示此浮点数的实际值。
    指数部分 占用8-bit的二进制数,可表示数值范围为0-255。 但是指数应可正可负,所以IEEE规定,此处算出的次方须减去127才是真正的指数。所以float的指数可从 -126到128.
    底数部分实际是占用24-bit的一个值,由于其最高位是e位 ,所以最高位省去不存储,在存储中只有23-bit。
    到目前为止, 底数部分 23位 加上指数部分 8位 使用了31位。那么前面说过,float是占用4个字节即32-bit,那么还有一位是干嘛用的呢?   还有一位,其实就是4字节中的最高位,用来指示浮点数的正负,当最高位是1时,为负数,最高位是0时,为正数。
       浮点数据就是按下表的格式存储在4个字节中:
                        Address+0       Address+1              Address+2              Address+3
    Contents     SEEE EEEE     EMMM MMMM     MMMM MMMM     MMMM MMMM      S: 表示浮点数正负,1为负数,0为正数
          E: 指数加上127后的值的二进制数
          M: 24-bit的底数(只存储23-bit)
    主意:这里有个特例,浮点数 为0时,指数和底数都为0,但此前的公式不成立。因为2的0次方为1,所以,0是个特例。当然,这个特例也不用人为去解决,编译器会自动去识别。


          通过上面的格式,我们下面举例看下4.5在计算机中存储的具体数据:
                        Address+0                 Address+1               Address+2            Address+3
    Contents        0x40                         0x90                           0x00                      0x00     接下来我们验证下上面的数据表示的到底是不是4.5,从而也看下它的转换过程。
    由于浮点数不是以直接格式存储,他有几部分组成,所以要转换浮点数,首先要把各部分的值分离出来。
                      Address+0      Address+1                  Address+2             Address+3
    格式         SEEEEEEE     EMMMMMMM       MMMMMMMM     MMMMMMMM
    二进制     01000000         10010000               00000000                00000000
    16进制     40                         90                            00                            00
           可见:
           S: 为0,是个正数。
           E:为 10000001   转为10进制为129,129-127=2,即实际指数部分为2。
           M:为 00100000000000000000000。 这里,在底数左边省略存储了一个1,使用 实际底数表示为 1.00100000000000000000000
           到此,我们吧三个部分的值都拎出来了,现在,我们通过指数部分E的值来调整底数部分M的值。调整方法为:如果指数E为负数,底数的小数点向左移,如果指数E为正数,底数的小数点向右移。小数点移动的位数由指数E的绝对值决定。
          这里,E为正2,使用向右移2为即得:
          100.100000000000000000000
    至次,这个结果就是4.5的二进制浮点数,将他换算成10进制数就看到4.5了,如何转换,看下面:
    小数点左边的100 表示为 (1 × 22) + (0 × 21) + (0 × 20), 其结果为 4。
    小数点右边的 .100… 表示为 (1 × 2-1) + (0 × 2-2) + (0 × 2-3) + ... ,其结果为.5 。
    以上二值的和为4.5, 由于S 为0,使用为正数,即4.5 。
    所以,16进制 0x40900000 是浮点数 4.5 。

    上面是如何将计算机存储中的二进制数如何转换成实际浮点数,下面看下如何将一浮点数装换成计算机存储格式中的二进制数。
    举例将17.625换算成 float型。
    首先,将17.625换算成二进制位:10001.101   ( 0.625 = 0.5+0.125, 0.5即 1/2, 0.125即 1/8 如果不会将小数部分转换成二进制,请参考其他书籍。) 再将 10001.101 向右移,直到小数点前只剩一位 成了 1.0001101 x 2的4次方(因为右移了4位)。此时 我们的底数M和指数E就出来了:
    底数部分M,因为小数点前必为1,所以IEEE规定只记录小数点后的就好,所以此处底数为   0001101 。
    指数部分E,实际为4,但须加上127,固为131,即二进制数 10000011 
    符号部分S,由于是正数,所以S为0.
    综上所述,17.625的 float 存储格式就是:
    0 10000011 00011010000000000000000
    转换成16进制:0x41 8D 00 00
    所以,一看,还是占用了4个字节。

    二、

    float一共32位,其结构定义如下:

    |-------- 31 -------|------------ 30-23 ------------ |------------ 22-0 ------------|

       符号位(sign)         指数部分(exp)                       小数部分(mag)

    sign:符号位就一位,0表示正数,1表示负数

    exp: 指数部分,无符号正数

    mag:小数部分,定点小数,小数点在最左边。

    float的表达式 :  pow(-1,sign)  *  (1+mag)  * pow(2,exp-127)

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    int main(int argc,char *argv[])
    {
        float f;
        int i;
        int sign;
        int exp;
        int mag;
        float d_mag;
        float f2;
    
        sscanf(argv[1],"%f",&f);
        //f=-0.12;
        i = *(int*)&f;
        sign = (i>>31)&0x01;
        exp = (i>>23)&0xFF;
        mag = i&0x7FFFFF;
        d_mag = 1.0f*mag/0x800000;
        f2 = (sign==0?1:-1)*(1+d_mag)*pow(2,exp-127);
        printf("float:f=%f
    ",f);
        printf("sign=%X,exp=%X,mag=%X
    ",sign,exp,mag);
        printf("float:f2=%f
    ",f2);
        return 0;
    }

    参考文献:http://en.wikipedia.org/wiki/IEEE_754-1985

    三、

    进制的算法:

     

     

    整数

     

     

    整数的二进制算法大家应该很熟悉,就是不断的除以

    2

    取余数,然后将余数倒序排

    列。

     

     

     

    小数

     

     

    小数的二进制算法和整数的大致相反,就是不断的拿小数部分乘以

    2

    取积的整数部

    分,然后正序排列。比如求

    0.9

    的二进制:

     

     

    0.9*2=1.8 

     

     

    0.8*2=1.6 

     

     

    0.6*2=1.2 

     

     

    0.2*2=0.4 

     

     

    0.4*2=0.8 

     

     

    0.8*2=1.6 

     

     

    … … 

     

    如此循环下去。因此我么得到的二进制小数也是无限循环的:

    0.11100110011... 

     

    从小数的二进制算法中我们可以知道,如果想让这种算法停止,只有在小数部分是

    0.5

    的时候才可以,但是很不幸,这类的小数很少。所以大部分小数是很难用二进制

    来精确表示的。

     

     

     

    ------------------------

    我是分割线

    ------------------------------

     

     

    OK

    ,有了上面的知识,我们进入正题:看看

    float

    类型在内存中是如何表示的。

     

     

     

     

    float

    类型又称为单精度浮点类型,在

     

     

     

    IEEE 754-2008

     

     

     

    中是这样定义它的结构的:

     

     

     

      S 

     

        EEEEEEEE 

     

         FFFFFFFFFFFFFFFFFFFFFFF 

     

    31 

      30 

           23 

       22 

                                  0

     

     

     

    float

    类型总共

    4

    个字节

    ——

    32

    位:

     

     

    1.

    符号位

     

    其中最左边的为符号位,

    0

    为正,

    1

    为负。

     

     

    2.

    指数

     

    接下来的

    E

    是指数,一共

    8

    位,也用二进制来表示。

     

     

    3.

    尾数

     

    最后的

    F

    是小数部分,

    尾数正是由这

    23

    位的小数部分

    +1

    位组成的。

    这个稍后解释)

     

     

     

    这里我们需要多说一下指数。虽然指数也是用

    8

    位二进制来表示的,但是

    IEEE

    在定义它的时候

    做了些手脚,使用了偏移来计算指数。

     

     

     

     

    IEEE

     

    规定,在

     

    float

     

    类型中,用来计算指数的偏移量为

    127

     

    。也就是说,如果你的指数实际是

    0

     

     

    那么在内存中存的就是

     

    0+127=127

     

    的二进制。稍后我们来看这个到底如何使用。

     

     

     

     

     

    好了,看了这么多,我们该演示一下计算机如何将一个十进制的实数转换为二进制的。就拿

     

    6.9

     

    这个数字来举例吧。

     

    -_-||!

     

     

     

     

     

    首先,我们按照上面说的方法,分别将整数和小数转换成对应的二进制。这样

    6.9

     

    的二进制表示

     

    就是

     

    110.1110011001100...

    。这里就看出来了,

     

    6.9

     

    转换成二进制,小数部分是无限循环的,这在

     

    现在的计算机系统上是无法精确表示的。这是计算机在计算浮点数的时候常常不精确的原因之

     

    一。

     

     

     

     

    其次,将小数点左移(或右移)到第一个有效数字之后。说的通俗些,就是把小数点移到第一个

     

    1

    之后。这样的话,对于上面

     

     

    110.1110011001100...

     

    我们就需要把小数点左

     

    2

     

    位,得到

     

    1.101110011001100...

     

     

     

     

     

     

    接下来的事情就有意思了。

    首先我们把得到的

     

    1.101110011001100..

    这个数,

     

    从小数点后第一位开

     

    始,数出

     

    23

     

    个来,填充到上面

     

    float

    内存结构的尾数部分(就是那一堆

     

    F

     

    的地方),我们这里数

     

    出来的就是

     

    10111001100110011001100

    。这里又要发生一次不精确了,小数点后超出

     

     

     

    23

     

    位的部

    分都将被舍弃,太惨了。

     

     

     

     

     

    不过,

     

    这里有一个可能让大家觉得特别坑爹的事情,

     

    就是小数点前面的

     

    1

     

    也不要了。仔细看看上

    面的内存结构,确实没有地方存放这个

     

    1

     

    。原因是这样的:

     

    IEEE

     

    觉得,既然我们大家都约定把

     

    小数点移动到第一个有效数字之后,那也就默认小数点前面一定有且只有一个

    1

     

    ,所以把这个

     

    1

     

    存起来也浪费,干脆就不要了,以后大家都这么默契的来就好。这也是为什么我上面说尾数是

     

    23

     

     

    +1

    位的原因。

     

     

     

     

     

    填充完尾数,

     

    该填充指数了。

    这个指数就是刚才我们把小数点移动的位数,

     

    左移为正,

     

    右移为负,

     

    再按照上面所说的偏移量算法,我们填充的指数应该是

     

    2+127=129

     

    。转换成

    8

     

    位二进制就是

    10000001

     

     

     

     

     

    最后,根据这个数的正负来填充符号位。我们这里是正数,所以填

     

    0

     

    。这样

     

    6.9

     

    的在内存中的存

     

    储结果就出来了:

     

     

     

     

     

     10000001 

     10111001100110011001100

     

     

    总结一下,实数转二进制

    float

    类型的方法:

     

     

    A. 

    分别将实数的整数和小数转换为二进制

     

    B. 

    左移或者右移小数点到第一个有效数字之后

     

    C. 

    从小数点后第一位开始数出

    23

    位填充到尾数部分

     

     

    D. 

    把小数点移动的位数,左移为正,右移为负,加上偏移量

    127

    ,将所得的和转换为二进制填

    充到指数部分

     

    E. 

    根据实数的正负来填充符号位,

    0

    为正,

    1

    为负

     

    如果需要把

    float

    的二进制转换回十进制的实数,只要将上面的步骤倒着来一边就行了

  • 相关阅读:
    thinkphp3.2生成二维码
    php实现图片下载
    yii2.0 Activeform表单部分组件使用方法
    Yii2美化confirm
    Yii2学习笔记之场景
    tp5页面输出时,搜索后跳转下一页的处理
    php页面输出时,js设置input框的选中值
    mac中使用rz,sz上传文件
    golang的命令行程序开发
    Sring MVC基于Java Config方式配置Mybatis, 无XML
  • 原文地址:https://www.cnblogs.com/cppskill/p/5487425.html
Copyright © 2020-2023  润新知