zoukankan      html  css  js  c++  java
  • 移码及浮点数在内存中的存储方式

    原博客搬移到:https://blog.csdn.net/u013171226/article/details/107680361

    First of all,we need to know how the decimal decimal is converted into a binary decimal.计算机根本就不认识10进制的数据,他只认识0和1,所以,10进制的小数在计算机中是用二进制的小数表示的。

    十进制的小数转化为二进制的小数的方法:

    可以简单的概括为正序取整,将十进制的小数部分乘以2,然后取整数部分。

    例如将0.2转化为二进制的小数,那么0.2*2=0.4,其整数部分是0,所以二进制小数的第一位为0,然后0.4*2=0.8,其整数部分是0,所以二进制小数的第二位为0,然后0.8*2=1.6,其整数部分是1,所以二进制小数的第三位是1,然后0.6*2=1.2,其整数部分是1,所以二进制小数的第四位是1。就这样一直计算下去,

    再例如8.25用二进制怎么表示:首先8用二进制表示是1000,然后0.25*2=0.5,其整数部分是0,所以二进制小数的第一位是0.然后0.5*2=1.0,所以二进制小数的第二位是1,所以8.25用二进制表示就是1000.01.

    移码:已知一个10进制数,怎么求它所对应的移码,补码的符号位取反就是移码,或者是加上128(假设是8)可以这么理解,但是这样说应该是不对的,补码应该是没有符号位的,确切的说应该是,补码的最高位取反就是移码,或者是加上128.

    例如,要求1的移码,1+128=129,,129=1000 0001或者用先求补码然后符号位取反的方法,那么先求出1的补码,1的补码是0000 0001,然后把他的符号位取反得到1000 0001,可以看到,两种方法求得的结果是一样的,

    再比如求-1的移码,-1+128=127=0111 1111,或者可以先求出-1的补码,-1的补码是1111 1111,那么把符号位取反得到移码0111 1111,两种方法得到的结果是一样的。

    另外,这里求解移码的方法和下面浮点数存储的时候求移码(阶码)的方法是不一样的,百度输入移码,上面一般会说,移码一般用作浮点数的阶码,但是这里说的移码的求法是补码加上128或者补码的符号位取反,而下面求浮点数阶码的时候是补码加上127

    另外,为什么要用移码表示阶码,而不用补码表示阶码,采用这种方式表示的目的是简化比较。因为,指数必须是有符号数才能表达很大或很小的数值,如果采用补码表示的话,首先小数是有符号的,如果指数也是有符号的,那么就不能简单的对浮点数进行大小比较。因为根据补码比较大小的话,要先转换成原码再比较大小,正因为如此,指数部分使用所谓的偏正值形式表示,实际值为原始值与一个固定值(8位的情况是127)的和。将它的值调整到一个无符号数的范围内以便进行比较。因为移码没有符号位,所以我们直接可以由移码的表示形式看出对应数值的大小,由此可见,移码是没有符号位的,移码的最高位不能看做是符号位而应看做是数值位,例如上面的1000 0001把最高位看成数值位才得到129。

    C语言浮点数存储方式

    浮点数(单精度float和双精度的double)在内存中是以二进制的科学计数法表示的, ,主要由三部分构成:符号位+阶码+尾数。float存储时使用4个字节,double存储时使用8个字节。各部分占用位宽如下所示:

                 符号位     阶码      尾数     长度

    float              1         8         23      32

    double          1         11        52      64

    符号位:0表示正数,1表示负数,注意这里的符号位是尾数的符号不是指数部分的符号,注意,所有的浮点数都是有符号数,不能定义unsigned float,这样定义的话,编译器会报错,当然有的编译器只是警告而不报错,

    阶码(指数部分):用于存储科学计数法中的指数数据,并且采用移位存储,

    尾数部分:尾数部分下面会详细说明,

    其中float的存储方式如下图所示:

     

    而双精度的存储方式为:

     

    关于尾数部分需要说明一下注意,尾数用的是原码,8.25用二进制表示可表示为1000.01,用二进制的科学计数法可以表示为1.00001*,120.5用二进制表示1111000.1用二进制的科学计数法可以表示为1.1110001*,任何一个数的科学计数法表示都为1.xxx*,因为尾数部分小数点前面都是1,所以可以将小数点前面的1省略,尾数部分就可以表示为xxxx,例如,0.5的二进制形式为0.1,由于规定正数部分必须为1,将小数点右移1位,则为1.0*2^(-1),而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其尾数部分就是 00000000000000000000000,由于将前面的1都省略了,所以23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里,那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位,

    关于阶码的求法:例如对于3,想求3对应的阶码是多少,方法是补码加上127,3+127=130=1000 0010(注意最高位也是数值位),再例如求6所对应的阶码,6+127=133=1000 0101,

    已知阶码,想求出原始的数据,方法是阶码减去127,例如1000 0010=130,然后130减去127=3,再例如1000 0101=133,然后133-127=6,

    下面举例说明浮点数在内存中的存储方式,知道了尾数的省略1的规定,知道了阶码的求法,接下来就可以看两个浮点数存储的例子了,例如8.25,首先转换为二进制的小数是1000.01=1.00001*2^3,首先确定符号位,8.25是正数因此符号位是0,然后求阶码,3的补码加上127的补码=1000 0010.然后是尾数部分,尾数的表示:去掉小数点前面的1,为00001,后面补充0至23位:000 0100 0000 0000 0000 0000最终8.25在内存里存储的二进制为:0100 0001  0000 0100  0000 0000  0000 0000,

    下面说一下,浮点数存储的时候几个特殊值的问题,

    1:如果指数部分不全为0并且不全为1:也就是0<指数部分<255,这种情况称为正规形式,这个时候浮点数就采用上面的介绍的规则计算,这个时候指数E就等于阶码减去127,求小数的时候在小数部分前面添加1,即1.xxxx。

    2:如果指数部分E全是0:这时浮点数的指数E等于1-127(而不是0-127,这是规定),求小数的时候不再加上第一位的1,而是还原为0.xxxxxx的小数,这样做是为了表示±0,以及接近于0的很小的数字,  关于0,IEEE规定0.0是一个特殊的数,阶码和尾数全为零来表示浮点的零。

    3:如果指数部分E全为1:这个时候如果尾数部分全是0,表示 ± ∞(正负取决于符号位S),如果尾数部分不全为0,表示这个数不是一个数(NaN,not a number)。

     

    指数的取值范围是多少:通过上面对于常规形式和非常规形式的分析,那么现在来看一下浮点数的指数取值范围是多少,指数部分是一个无符号整数,这意味着,对于float类型,由于指数部分是8位,因此它的取值范围是0到255,当阶码是0的时候,根据上面的规定,这个时候指数是1-127=-126,而当阶码等于255的时候又是特殊值,所以不能算,当阶码是254的时候,指数等于254-127=127,因此可以不恰当的说指数的取值范围是-126到127. 

    关于float型变量所能表示的数的范围的问题:先来分析一下float类型所能表示的绝对值最大的数,上面分析了指数的最大值是127,而尾数部分的最大值就是23个1,这个时候就是float所能表示的最大值,这个值是1.111 1111 1111 1111 1111 1111*2^127=3.4*10^38.当符号位是1时表示负数-3.4*10^38。

    float所能表示的绝对值最小的数是多少呢,当指数部分全是0的时候,这个时候指数=1-127=-126,然后尾数部分的最小值000 0000 0000 0000 0000 00001,这个时候小数是0.000 0000 0000 0000 0000 0001.因此浮点数所能表示的绝对值最小的数应该是1*2^-149 。(这个不太确定,好像是求错了,)

    关于浮点数的上溢和下溢: 在C Primer Plus的第三章后面有一个编程练习题,“通过试验的方法,观察系统如何处理整数上溢,浮点数上溢,浮点数下溢的情况”参考答案是这样的;

    #include<stdio.h>

    int main(void)

    {

    unsigned int a=4294967295;

        float b=3.4E38;

        float c=b*10;

        float d=0.1234E-2;

        printf("%u+1=%u ",a,a+1);

     printf("%e*10=%e ",b,c);

     printf("%f/10=%f ",d,d/10);

     return(0);

    }

    /*

    在VC++6.0中的输出结果为:

    4294967295+1=0

    3.400000e+038*10=1.#INF00e+000

    0.001234/10=0.000123   丢失了一个有效数字

    Press any key to continue

    */

    刚开始的时候不理解,0.001234/10=0.0001234,那么不就是1.234E-4吗,浮点数完全可以存储这个数,怎么就会丢失有效位了呢,再仔细看了一下才发现,程序里面的printf函数用的是%f输出,%f是普通的十进制输出,并不是科学计数法输出,因此会丢失有效位,而如果把%f换成%e或者%E,那么0.001234除以10之后是不会丢失有效位的,

    再例如:

    #include<stdio.h>

    int main(void)

    {

        int a = 0x00000009;

        float b;

        b = (float)a;

        printf("%e ",a);

        return 0;

    }

    如果用%f输出的话,那么得到的结果将是0.000000.因为将0x00000009拆分,得到第一位符号位s=0,后面8位的指数E=00000000,最后23位的有效数字M=000 0000 0000 0000 0000 1001。由于指数E全为0,所以求指数的时候是1-127=-126,而求小数部分的时候前面不是添加1而是添加零,因此,浮点数V就写成:V=(-1)^0×0.00000000000000000001001×2^(-126)=1.001×2^(-146),这个时候用%f输出的话,得到的结果就是0了,

    浮点数存储的时候存在误差的问题:举例说明,2.2,将十进制的小数转换为二进制的小数的方法是:将小数*2,取整数部分。

       0.2×2=0.4,所以二进制小数第一位为0.4的整数部分0;

       0.4×2=0.8,第二位为0.8的整数部分0;

       0.8×2=1.6,第三位为1;

       0.6×2=1.2,第四位为1;

       0.2×2=0.4,第五位为0;

       ...... 这样永远也不可能乘到=1.0,得到的二进制是一个无限循环的排列 00110011001100110011...

    对于单精度数据来说,尾数只能表示24bit的精度,所以2.2的 float存储为:

    但是这种存储方式,换算成十进制的值,却不会是2.2。因为在十进制转换为二进制的时候可能会不准确,这样就导致了误差问题!所以在浮点数表示中,某些数在存储的时候就会存在误差,而对于有些数据(如2.25),在将十进制转换为二进制表示的时候恰好能够计算完毕,所以这个误差就不会存在。

    怎么比较两个浮点数的大小:很多C程序员的笔试会有浮点数比较大小的题目,因为浮点型只是一个近似值,也就是一个值可能表示一个范围区间,这样的表达方式就使得对浮点型数据采用做差判断是否等于0的方法进行比较可能不合理,只有通过比较一个数是否在这个小的范围内,因此在计算值比较两个浮点数变量不能通过做差是否等于零来判断。而只能通过如下的方式判断:

    const float ESPSION = 0.000001;

    if((x-y)>=-0.000001&& (x-y)<=0.000001)

    这种实现方式是基本的比较方式,这种判断方法刚好就是判断变量是否处于一个范围内,这里的范围是-0.000001<x<0.000001。判断一个值是否为0的方式用大于-0.000001小于0.000001来判断,这样浮点的0是一个很接近于0数,但不是0,这样就不会引发除0错误,0.0其实不是0,当x落在了±0.000001之内,x + 1.0 = 1.0,就是这么规定的。x在此范围之内的话,都被计算机认为是0.0 。

    在看C Primer Plus的时候,有一个课后题是关于浮点数的上溢和下溢的问题,看了答案之后感觉不明白,于是去查一下浮点数的存储问题,这一查才发现浮点数的存储牵扯到的东西还挺多的,由于智商平平,浮点数的存储这个小问题前前后后竟然耽误了10天左右的时间,看了一个又一个的博客,最后才算对浮点数的存储稍微了解了,下面是自己做的笔记,部分内容是复制的别人的博客,无意侵权,纯粹是为了做笔记。

    笔记中如有错误恳。各位前辈指正,好让我及时改正,以免我把错误的东西当成正确的了,

    作者:cumtchw
    出处:http://www.cnblogs.com/cumtchw/
    我的博客就是我的学习笔记,学习过程中看到好的博客也会转载过来,若有侵权,与我联系,我会及时删除。

  • 相关阅读:
    网页加速的14条优化法则 网站开发与优化
    .NET在后置代码中输入JS提示语句(背景不会变白)
    C语言变量声明内存分配
    SQL Server Hosting Toolkit
    An established connection was aborted by the software in your host machine
    C语言程序设计 2009春季考试时间和地点
    C语言程序设计 函数递归调用示例
    让.Net 程序脱离.net framework框架运行
    C语言程序设计 答疑安排(2009春季 110周) 有变动
    软件测试技术,软件项目管理 实验时间安排 2009春季
  • 原文地址:https://www.cnblogs.com/cumtchw/p/4539558.html
Copyright © 2011-2022 走看看