zoukankan      html  css  js  c++  java
  • 浮点数表示及其实现

    我两年前就知道不应该用==号来判断浮点数的相等了,因为存在一个精度的问题,但是一直以来,都没怎么在乎这些东西,而实际上,我对于浮点数的结构,虽然了解,但并不清晰. 作为一个C++爱好者,应该尽量搞清楚每一个问题,所以我搞清楚了浮点数的内在表示及实现.在没有大问题的情况下,一切以易于理解和记忆为标准.

    首先说一下原,反,补,移码. 移码其实就等于补码,只是符号相反. 对于正数而言,原,反,补码都一样, 对负数而言,反码除符号位外,在原码的基础上按位取反,补码则在反码的基础之上,在其最低位上加1,要求移码时,仍然是先求补码,再改符号.

    浮点数分为float和double,分别占4,8个字节,即32,64位. 我仅以32位的float为例,并附带说double.

    在IEEE754标准中,规定,float的32位这样分:

        符号位(S)

    1

    阶码(E)

     8

    尾数(M)

    23

     这里应该注意三点:   A,阶码是用移码表示的,这里会有一个127的偏移量,它的127相当于0,小于127时为负,大于127时为正,比如:10000001表示指数为129-127=2,表示真值为2^2,而01111110则表示2^(-1).

                                         B, 尾数全都是小数点后面的数,

                                         C, 但尾数中省略了一个1,因此尾数全为0时,也是1.0...00;

    接下来只要说明几个问题就明白了,以123.456为例,表示为二进制就是:N (2) = 1111011. 01110100101111001 ,这里,会右移6位,得到N (2) = 1.111011 01110100101111001*2^6; 这种形式就可以用于上图中的表示格式了.              

     符号位(S) 

              0 

    阶码(E) 00000110

    尾数(M)11101101110100101111001

    注意到,上面的阶码第一位为0表正,尾数比N(2)表示的第一位少了个1,这就是上面说的默认为第一位为1. 由于在将十进制转为二进制的过程中,常常不能正好转得相等, (当然,像4.0这样的就不会有损失,而1.0/3.0这样的必然损失),所以就产生了浮点数的精度问题, 实际上,小数点后的23位二进制数,能影响的十进制数的前8位,这是为什么呢?一般人在这时往往迷迷胡胡了,其实很简单,在上面表示的尾数中,是二进制的,小数点后有23位,最后一位的值为1时,它就是1/2^22=0.000000238实际取的时候肯定是0.0000002,也就是说,对于一个float型的浮点数,其有效的位数是从左到右数7位(包括缺省的1才是7位),当到达上面这个第8位时,就不可靠了,但我们的VC6可以输出最长的1.0/3.0为0.33333333333333331,这主要是编译器的问题了, 而并不是说浮点数小数点后的16位都有效. 如果不信的话,可以去试一下double类型的1.0/3.0, 得到的也将是小数点后17位.                                                                                                  ..另外,编译器或电路板一般都有"去噪声"的"修正"能力,它能够使得超过7位的十进制数即使无效了也不会变得离谱,这也是上面为什么一直都是输出333而不是345之类的,. 可以这样试一下:

    float f=123456789;
     cout<<f<<endl;//这里肯定得到123456789.

    这里有一个被人遗忘的问题,就是10进制小数怎么变为2进制小数,其实很简单,就是将10进的小数部分不断乘以2,进位时就将对应的2进制位写入1. 因此将上面的N (2) = 1.111011 01110100101111001*2^6;再转回十进制数时,很可能已经不再是123.456了. 好,精度问题应该说清楚了. 下面说示数范围.

    阶码的示数位数是8位移码,最大为127最小为-127,这里的127用来作为2的指数,因此为2^127,约等于 1.7014*10^38, 而我们知道,float的示数范围约为-3.4*10^38-------3.4*10^38, 这是因为尾数的24位(默认第一位为1)全为1是,非常接近2,  1.11..11很明显约为2,因此浮点数的范围就出来了.

    double的情况与float完全相似,只是它的内在形式是

        符号位(S)

              1

    阶码(E)

    11

    尾数(M) 

       52

    主要的区别在于它的阶码有11位了, 这就有2^1023约等于 0.8572*10^308, 尾数53位约为2,故double的示数范围约为 -1.7*10^308.------1.7*10^308.  至于其精度,同样,1.0/2^51=4.4*10^(-16).小数点后15位有效,加上缺省的那一位,因此对于double浮点数,从左到右的16位数都是可靠的.

    有时,我们会听到"定点小数"这个词,单片机(如手机等)一般只使用定点数,迷糊的时候,我们会以为 float  a=23.4; 这种是定点小数, float a=2.34E1这种为浮点数,其实这是错误的, 上面只是同一个浮点数的不同表示,都是浮点数. 定点小数是有这种提法,认为整就是定点小数,小数点定在个位后面,小数部分为0.也可认为纯小数是定点小数,但它只能表示小于1的纯小数.

    然后再说一下C/C++中的几个函数, C++中默认输出小数点后的5位小数,但可以设置,有两种方法:调用setpression或者使用cout.pression,但效果是不同的:

     float mm=123.456789f;
     cout<<mm<<endl;  //123.457           虽说默认为不数点后5位,但只是整数部分只有一位才这样.
     setprecision(10);                               //设置小数点后的位数,但当整数部分有两位时,与默认情况没什么两样,不起作用.
     cout<<mm<<endl;  //123.457
     cout.precision(4);                              //设置总的位数.
     cout<<mm<<endl;  //123.4     总之效果是比较怪的,个人认为虽然这样显得不够确定,但实为硬件系统所限.无可厚非.

    对于0的实际表示,有人认为+0一般能绝对为0,而-0则可能表示一个极小的数.  为此,本人想到了一种很好的验证办法,证明了不管+0还是-0,它都是2^(-127),代码如下:

     float fDigital = 0.0f;        
     unsigned long nMem;// 临时变量,用于存储浮点数的内存数据
     // 将内存按位复制到临时变中,以便取用,此时的nMem并不等于fDigital了,它是按位复制的。
     nMem = *(unsigned long*)&fDigital;
     cout<<nMem<<endl;  //一般得到一个很大的整数.

     bitset<32>mybit(nMem);//妙在此处,这里的输出就是32float的内存表示了.终于完全直观地看到了.
     cout<<mybit<<endl;   //00000000000000000000000000000000 用-0.0来试,也是如此.

    如果你还认为上面那一长串的0表示的是绝对的0,那么请重新看本文. 事实上,本人的这种做法是比较巧妙的,将上面的fDigital用任何其它浮点数表示,这个bitset数都可以反映出它的内存表示.

    有移码表示阶码有是有原因的,主要是移码便于对阶操作,从而比较两个浮点数的大小. 这里要注意的是,阶码不能达到11111111的形式,IEEE规定,当编译器遇到阶码为0XFF时,即调用溢出指令.  总之,阶码化为整数时,范围是:-127~127.

    最后,有一个往往高手也汗颜的地方,一定要记住,浮点数没有无符号型的usinged float/double是错误的.

    本人才疏学浅,欢迎批评指正.

    ---转载或者使用代码,请注明原作者 This is bill
  • 相关阅读:
    windwos8.1英文版安装SQL2008 R2中断停止的解决方案
    indwows8.1 英文版64位安装数据库时出现The ENU localization is not supported by this SQL Server media
    Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds
    SQL数据附加问题
    eclipse,myeclipse中集合svn的方法
    JAVA SSH 框架介绍
    SSH框架-相关知识点
    SuperMapRealSpace Heading Tilt Roll的理解
    SuperMap iserver manage不能访问本地目的(IE9)
    Myeclipse中js文件中的乱码处理
  • 原文地址:https://www.cnblogs.com/ScytheWH/p/4642217.html
Copyright © 2011-2022 走看看