精华内容
下载资源
问答
  • 深入C++浮点数无效值定义与判定的解决办法
    千次阅读
    2021-05-23 01:09:59

    深入C++浮点数无效值定义与判定的解决办法

    在实际计算中可能会出现浮点型的无效数据,格式化输出到文件中内容为1.79769e+308,输出到界面就是很大一串数据。这两种方式无论哪种在阅读和查找时就不是很方便。

    无效值定义如下:#define InvalidDouble *(double*)("\xff\xff\xff\xff\xff\xff\xef\x7f")

    double  price;

    .........(计算步骤省掉)

    price=GetPrice(); //获取数据

    通过下面方式对浮点型的数据进行判定

    (price==InvalidDouble)?0:price

    时间: 2013-05-13

    任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100.则在Intel CPU架构的系统中,存放方式为  10000100(低地址单元) 00000100(高地址单元),因为Intel CPU的架构是小端模式.但是对于浮点数在内存是如何存储的?目前所有的C/C++编译器都是采用IEEE所制定的标准浮点格式,即二进制科学表示法.在二进制科学表示法中,S=M*2^N 主要由三部分构成:符号位+阶码(N)+尾数(M).对于flo

    6b5aa0751a6f1e6a9d0b665a02cc1f46.gif

    上节课 简单介绍了浮点数.计算机程序中的浮点数分为单精度浮点数和双精度浮点数. 单精度和双精度精确的范围不一样. 计算机里的最基本的存储单位用位(bit)来表示.bit只能用来存储0或1. 稍大一点的单位是字节(Byte,简写为B). 再大一级的是千字节(kilo Bytes),用k来表示. 再大一级的单位是兆字节(Mega Bytes),用M来表示.一张照片的大小通常为1~3M. 再大一级的单位为G.一部高清电影的大小通常为1~2G. 再大一级的单位为T. 换算关系为: 1B = 8bit 1

    浮点数在内存中的存储机制和整型数不同,其有舍入误差,在计算机中用近似表示任意某个实数.具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学记数法.所以浮点数在运算过程中通常伴随着因为无法精确表示而进行的近似或舍入.但是这种设计的好处是可以在固定的长度上存储更大范围的数.1.将字符串转换为float.double过程存在精度损失,只是float.double各自损失的精度不相同而已std::string str="8.2&

    代码如下所示: 复制代码 代码如下: #include       #include       #include       #include       #include       #include       #include       using namespace std; str

    C++格式化输出浮点数 复制代码 代码如下: #include using std::cout;using std::endl;using std::fixed;using std::scientific;int main(){   double x = 0.001234567;   double y = 1.946e9;   cout << "Displayed in default format:" << endl

    在实际计算中可能会出现浮点型的无效数据,格式化输出到文件中内容为1.79769e+308,输出到界面就是很大一串数据.这两种方式无论哪种在阅读和查找时就不是很方便.在开发中遇见浮点型数据无效值得判定. 无效值定义如下:#define InvalidDouble *(double*)("\xff\xff\xff\xff\xff\xff\xef\x7f")double  price;.........(计算步骤省掉)price=GetPrice(); //获取数据 通过下面方式对浮点型的无

    SQL SERVER将某一列字段中的某个值替换为其他的值 update 表名 set 列名 = REPLACE( 列名 ,'aa','bb') SQL SERVER"函数 replace 的参数 1 的数据类型 ntext 无效"解决办法 UPDATE 表名 SET 列名= REPLACE(CAST 列名 AS varchar(8000)),'aa','bb')

    C++编程优与Pascal的原因之一是C++中存在STL(标准模板库).STL存在很多有用的方法. C++模板库中的许多方法都需要相关参数有序,例如Sort().显然,如果你想对一个集合进行排序,你必须要知道集合中的对象,那个在前那个在后.因此,学会如何定义比较方法是非常重要的. C++模板库的许多容器需要相关类型有序,例如set 和priority_queue. 这篇文章旨在告诉大家如何为一个类定义一个排序方法,以便在STL容器或者方法中使用. 作为一个C++程序员,

    前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学习Webapi的园友们速速动起来,跟着博主一起来学习吧.之前分享过一篇C#进阶系列--WebApi接口传参不再困惑:传参详解,这篇博文内容本身很基础,没想到引起很多园友关注,感谢大家的支持.作为程序猿,我们都知道参数和返回值是编程领域不可分割的两大块,此前分享了下WebApi的传参机制,今天再来看看WebApi里面另一个重要而又基础的知识点:返回值.还是那句话:本篇针对初初使用WebApi的同学们,比较基础,有兴趣的且看看.

    Cannot set reactive property on undefined, null, or primitive value: //无法对未定义的值.空值或基元值设置反应属性: 比如我们在写一个表单,提交成功后要清空表单 我把数据绑在上面了方便看,确定提交成功我们一般要清空input,而我在js里开始这样写 我写的时候提交成功直接把这个对象变成空了,再次打开弹窗就会报这类型错 上面绑的数据已经成空了,所有找不到这个对象包括key ,value 清空的话,单个清空,或者直接对象为空 或者

    过程1:带返回值: drop procedure if exists proc_addNum; create procedure proc_addNum (in x int,in y int,out sum int) BEGIN SET sum= x + y; end 然后,执行过程,out输出返回值: call proc_addNum(2,3,@sum); select @sum; 过程2:不带返回值: drop procedure if exists proc_addNum; create

    前言: 我们知道java的hashmap中使用最多的是put(...),get(...)以及remove()方法,那么在angularJS中如何创造(使用)这样一个对象呢 思路分析: 我们知道在java中可以采用链式访问和"[]"访问hashmap的某一个值 具体实现: 链式访问: .factory('ParamsServices', function () { var params = {}; return { get: function (key) { return params.

    本文实例讲述了PHP7.1方括号数组符号多值复制及指定键值赋值用法.分享给大家供大家参考,具体如下: PHPer 们可能都知道 list 的用法,简单来说就是可以在一个表达试里通过数组对多个变量赋值: $values = array('value1', 'value2'); $list($v1, $v2) = $values; 感觉是不是很方便呢?在 PHP 7.1 中,还能更省事儿: [$v1, $v2] = ['foo', 'bar']; 这还不是最给力的,在 PHP 7.1 里我们还可以指

    最近在做玩家蚂蚁项目的时候遇到了一个评分显示的小功能,这个在前端显示的星星是用class样式来进行控制的 class =" real-star comment-stars-width5 " 数据库里记录的信息只有一个评分1-5.我们如果用一般的php方法直接在class里面用 comment-stars-width{$score}这样是行不通的.在查了很多资料和做过很多尝试以后,确定了一个可行的方法 我们使用vue的:class来进行class的绑定.:class="`com

    问题 查看 tensorflow api manual 时,看到关于 variable.read_value() 的注解如图: 那么在 tensorflow 中,variable的值 与 variable.read_value()的值 到底有何区别? 实验代码 # coding=utf-8 import tensorflow as tf # Create a variable. w = tf.Variable(initial_value=10., dtype=tf.float32) sess =

    更多相关内容
  • C语言浮点数

    千次阅读 2019-04-13 19:56:06
    C语言规定了3种浮点数,float型、double型和long double型,其中float型占4个字节,double型占8个字节,longdouble型长度要大于等于double型,本文档将以float型为例进行介绍,double型和long double型只是比float型...

    C语言规定了3种浮点数,float型、double型和long double型,其中float型占4个字节,double型占8个字节,longdouble型长度要大于等于double型,本文档将以float型为例进行介绍,double型和long double型只是比float型位数长,原理都是一样的。

     

    float型可以表示的范围是-3.402823466e38~3.402823466e38,而作为同为4个字节的定点数却只能表示-2147483648~2147483647的范围,使用同样的内存空间,浮点数却能比定点数表示大得多的范围,这是不是太神奇了?既然浮点数能表示这么大的范围,那么我们为何不使用浮点数来代替定点数呢?

     

    先不说浮点数实现起来比较复杂,有些处理器还专门配置了硬件浮点运算单元用于浮点运算,主要原因是浮点数根本就无法取代定点数,因为精度问题。鱼和熊掌不可兼得,浮点数表示了非常大的范围,但它失去了非常准的精度。在说明精度问题前,我们先了解一下浮点数的格式。

     

    ANSI/IEEEStd 754-1985标准

    IEEE 754是最广泛使用的二进制浮点数算术标准,被许多CPU与浮点运算器所采用。IEEE754规定了多种表示浮点数值的方式,在本文档里只介绍32bits的float浮点类型。它被分为3个部分,分别是符号位S(sign bit)、指数偏差E(exponent bias)和小数部分F(fraction)。

    其中S位占1bit,为bit31。S位为0代表浮点数是正数,S位为1代表浮点数是负数,比如说0x449A522C的S位为0,表示这是一个正数,0x849A522C的S位为1,表示这是一个负数。

    E位占8bits,为bit23~bit30。E位代表2的N次方,但需要减去127,比如说E位为87,那么E位的值为2(87-127)=9.094947017729282379150390625e-13。

    F位占23bits,为bit0~bit22。F位是小数点后面的位数,其中bit22是2-1=0.5,bit21是2-2=0.25,以此类推,bit0为2-23=0.00000011920928955078125。但F位里隐藏了一个1,也就是说F位所表示的值是1+(F位bit22~bit0所表示的数值),比如说F位是0b10100000000000000000001,只有bit22、bit20和bit0为1,那么F位的值为1+(2-1+2-3+2-23),为1.62500011920928955078125。

     

     综上所述,从二进制数换算到浮点数的公式为:。但还有几个特殊的情形:

     

    1、若E位为0并且F位也为0时表示浮点数0,此时浮点数受S位影响,表现出+0和-0两种0,但数值是相等的。比如二进制数0x00000000表示+0,二进制数0x80000000表示-0。

    2、若E位为0并且F位不为0时浮点数为,注意F位是0.xx格式而不是1.xx格式,比如0x00000001的浮点数为=7.0064923216240853546186479164496e-46,而不是。一旦E为不为0,从0变为1,不是增加2倍的关系,因为公式改变了。

    3、若E位为255并且F位不为0时表示非数值,也就是说是非法数,例如0x7F800001。

    4、 若E位为255并且F位为0时表示无穷大的数,此时浮点数受S位影响,例如0x7F800000表示正无穷大,0xFF800000表示负无穷大。当我们使用1个数除以0时,结果将被记作0x7F800000。

           

    浮点型在多个处理器间通信时,传递的数值是它的二进制数,比如说1234.5678这个浮点数的二进制数是0x449A522B,如果使用串口发送的话,就会发现串口里发送的是0x44、0x9A、0x52和0x2B这4个数(发送的顺序也可能是逆序,这与约定的字节序有关,与浮点格式无关),接收端接收到这4个数字后再组合成0x449A522B,按照IEEE 754的定义被解析成1234.5678,这样就实现浮点数通信了。如果两个处理器所使用的浮点数规则不同,则无法传递浮点数。

           

    浮点数的换算

    下面来看看浮点数与二进制数如何转换。

    1、二进制数换算成浮点数:

     

    假如在内存中有一个二进制数为0x449A522C,先将十六进制转换成二进制,如下:

    0100 0100  1001  1010 0101  0010  0010 1100

     

    按照SEF的格式分段,如下:

    0 10001001  00110100101001000101100

     

    这个数值不是特殊的情形,可以按照公式(-1)S×2E-127×(1+F)转换。S位的值为(-1)0=1,E位的值为2137-127=1024。F位的值为1+2-3+2-4+2-6+2-9+2-11+2-14+2-18+2-20+2-21= 1.205632686614990234375。最终结果为1×1024×1.205632686614990234375=1234.56787109375。

     

    其中F位比较长,使用二进制方式转换比较麻烦,也可以先转换成十六进制再计算,转换为十六进制如下:

    0011 0100  1010  0100 0101  1000

    0x3  0x4   0xA   0x4  0x5   0x8

     

    F位为23bits,需要在最后补一个0凑成24bits,共6个十六进制数。F位的值为1+3×16-1+4×16-2+10×16-3+4×16-4+5×16-5+8×16-6=1.205632686614990234375,与上面使用二进制方法得到的结果相同。

     

    2、浮点数换算成二进制数:

     

    下面我们将-987.654e30换算成二进制数。我们先不看符号位,将987.654e30归一化为整数部分为1的形式,也就是写作987.654e30=2E-127×(1+F)的标准形式,其中E=log(987.654e30)/log2+127=109.6+127,取E位的整数值为109+127=236,再求F=987.654e30/2236-127-1=0.52172193,这个小数位数保留8位就够了,已经超出了7位的精度。然后我们求小数部分的二进制数,这个转换就没啥好说的了,依次减去2的幂,从2-1一直到2-23,够减的位置1,不够减的位置0,例如,2-1为0.5,0.52172193-0.5=0.02172193,F位的bit22置1,2-2为0.25,0.02172193不够减,F位的bit21置0,2-3为0.125,0.02172193不够减,F位的bit20置0,2-4为0.0625,0.02172193不够减,F位的bit19置0……,一直算到F位的bit0,这样就得到F位的数值。

     

    如果觉得使用二进制方式转换太麻烦的话也可以使用十六进制进行转换。16-1为0.0625,0.52172193/0.0625=8.3,说明够减8个,记做0x8,0.52172193-0.0625×8=0.02172193,16-2为0.00390625,0.02172193/0.00390625=5.6,说明够减5个,加上刚才的0x8记做0x85,以此类推:

     

     

    16的-N次幂

    被减数

    十六进制数

    减后的数

    1

    0.0625

    0.52172193

    0x8

    0.02172193

    2

    0.00390625

    0.02172193

    0x85

    0.00219068

    3

    0.000244140625

    0.00219068

    0x858

    0.000237555

    4

    0.0000152587890625

    0.000237555

    0x858F

    0.0000086731640625

    5

    0.00000095367431640625

    0.0000086731640625

    0x858F9

    0.00000009009521484375

    6

    0.000000059604644775390625

    0.00000009009521484375

    0x858F91

     

     

    一直凑够23bits,也就是6个十六进制,得到0x858F91,换算成二进制如下所示:

    1000 0101  1000  1111 1001  0001

     

    由于只有23bits有效,因此需要去掉最后一个bit,二进制本着0舍1入的原则,变成

    1000 0101  1000  1111 1001  001

     

    最后需要再补上前面的S位和E位。由于是负数,S位为1。E位为236,二进制形式为1110 1100,将S、E、F位组合在一起就形成了:

    1 1110 1100  1000  0101 1000  1111  1001 001

     

    从左边最高位开始,4个一组合并成十六进制:

    1111 0110  0100  0010 1100  0111  1100 1001

     

    换算成十六进制为:

    0xF  0x6   0x4   0x2  0xC   0x7  0xC  0x9

    所以-987.654e30换算成二进制数为0xF642C7C9。

           

    浮点数的精度

     

    在前面的讲解中可以看到1.xx这个数量级的最小数是2-23,对应的十进制数值为1.00000011920928955078125,可以精确表示到小数点后23位,但有些C语言书上却说float型的有效位只有6~7位,这是为什么?

     

    这是因为二进制小数与十进制小数没有完全一一对应的关系,二进制小数对于十进制小数来说相当于是离散的而不是连续的,我们来看看下面这些数字:

    二进制小数        十进制小数

    2-23       1.00000011920928955078125

    2-22       1.0000002384185791015625

    2-21       1.000000476837158203125

    2-20       1.00000095367431640625

    2-19       1.0000019073486328125

    2-18       1.000003814697265625

     

    不看S位和E位,只看F位,上表列出了1.xx这个数量级的6个最小幂的二进制小数,对应的十进制在上表的右边,可以看到使用二进制所能表示的最小小数是1.00000011920928955078125,接下来是1.0000002384185791015625,这两个数之间是有间隔的,如果想用二进制小数来表示8位有效数(只算小数部分,小数点前面的1是隐藏的默认值)1.00000002、1.00000003、1.00000004...这些数是无法办到的,而7位有效数1.0000001可以用2-23来表示,1.0000002可以用2-22来表示,1.0000003可以用2-23+2-22来表示。从这个角度来看,float型所能精确表示的位数只有7位,7位之后的数虽然也是精确表示的,但却无法表示任意一个想表示的数值。

     

    但还是有一些例外的,比如说7位有效数1.0000006这个数就无法使用F位表示,二进制小数对于十进制小数来说相当于是离散的,刚好凑不出1.0000006这个数,从这点来看float型所能精确表示的位数只有6位。至于5位有效值的任何数都是可以使用F位相加组合出来的,即便是乘以E位的指数后也是可以准确表示出来的。

     

    因此float型的有效位数是6~7位,但这个说法应该不是非常准确,准确来说应该是6位,C语言的头文件中规定也是6位。

     

    对于一个很大的数,比如说1234567890,它是F位乘上E位的系数被放大了的,但它的有效位仍然是F位所能表示的6位有效数字。1234567890对应的二进制数是0x4E932C06,其中F位的数值为1.1497809886932373046875,E位的数值为230=1073741824,1073741824×1.1497809886932373046875=1234567936,对比1234567890,也只有高7位是有效位,后3位是无效的。int型定点数可以准确的表示1234567890,而float浮点数则只能近似的表示1234567890,精度问题决定了float型根本无法取代int型。

           

    浮点数的比较

     

    float型的有效位数是6位,那么我们在用float型运算时就要注意了,来看下面这段程序:

     

    [cpp] view plaincopyprint?

    1. #include <stdio.h>  
    2.    
    3. int main(void)  
    4. {  
    5.    float a = 9.87654321;  
    6.    float b = 9.87654322;  
    7.      
    8.    if(a > b)  
    9.     {  
    10.        printf("a > b\n");  
    11.     }  
    12.    else if(a == b)  
    13.     {  
    14.        printf("a == b\n");  
    15.     }  
    16.    else  
    17.     {  
    18.        printf("a < b\n");  
    19.        }  
    20.    
    21.    return 0;  
    22. }  


     

     

     

    按照我们平时的经验来说这段程序应该走a < b的分支,但程序运行的结果却走了a == b的分支,原因就是float型的精度问题,float型无法区分出小数点后的第8位数,在内存中,a和b的二进制数都是0x411E0652,因此就走了a == b的分支。

     

    某些编译器在编译时会发现a和b的值超出了浮点数的精度,会产生一个告警,提示数据超过精度,但有些编译器则不会做任何提示。最可怕的是有一类编译器,调试窗口里显示的长度超出float型的精度,比如说a的值显示为9.87654321,b的值显示为9.87654322,但在运行时,硬件可不管这套,硬件认为这2个数都是0x411E0652,因此实际运行结果是a == b的分支。

     

    由于精度这个问题的限制,我们在浮点数比较时就需要加一个可接受的精度条件来做判决,比如说上面的这个问题,如果我们认为精度在0.00001就足够了,那么a - b之差的绝对值只要小于0.00001,我们就认为a和b的值是相等的,大于0.00001则认为不等,还要考虑到a - b正负等情况,因此可以将上面的程序改写为:

     

    [cpp] view plaincopyprint?

    1. #include <stdio.h>  
    2.    
    3. int main(void)  
    4. {  
    5.    float a = 9.87654321;  
    6.    float b = 9.87654322;  
    7.    
    8.    if(a - b < -0.00001)  
    9.     {  
    10.        printf("a < b\n");  
    11.     }  
    12.    else if(a - b > 0.00001)  
    13.     {  
    14.        printf("a > b\n");  
    15.     }  
    16.    else  
    17.     {  
    18.        printf("a == b\n");  
    19.        }  
    20.    
    21.    return 0;  
    22. }  

     

     

     

    例子中a和b之差的绝对值小于0.00001,因此认为a和b是相等的,运行程序,也正确的打印了a == b。

      

    为什么我们要做一个精度的限定?这是因为我们在应用中的精度往往要低于硬件的6位精度。

     

    浮点数的加减

     二进制小数与十进制小数之间不存在一一对应的关系,因此某些十进制很整的加减法小数运算由二进制小数来实现就表现出了不整的情况,来看下面的例子:

     

    [cpp] view plaincopyprint?

    1. #include <stdio.h>  
    2.    
    3. int main(void)  
    4. {  
    5.    float a = 10.2;  
    6.    float b = 9;  
    7.    float c;  
    8.    
    9.     c= a - b;  
    10.    
    11.        printf("%f\n", c);  
    12.    
    13.    return 0;  
    14. }  

     

     

     

    如果用十进制计算的话变量c应该为1.2,在Visual C++ 2010环境下实验输出为1.200000,但实际上c变量的值是1.1999998,只不过是在输出时被四舍五入为1.200000罢了。在内存中c变量的二进制数是0x3F999998,它对应的浮点数是0.19999980926513671875。如果我们将printf函数%f的格式改为%.7f格式,就会看到c变量输出的值是1.1999998。

     

    两个数量级相差很大的数做加减运算时,数值小的浮点数会受精度限制而被忽略,看下面的例子:

     

    [cpp] view plaincopyprint?

    1. #include <stdio.h>  
    2.    
    3. int main(void)  
    4. {  
    5.    float a = 987654321;  
    6.    float b = 987.654322;  
    7.    float c;  
    8.    
    9.     c= a + b;  
    10.    
    11.    printf("%f\n", c);  
    12.    
    13.    return 0;  
    14. }  


     

     

    Visual C++ 2013上的计算结果为987655296.000000,而实际的真实值为987655308.654322,二进制值为0x4E6B79B2对应987655296,就是987655296.000000。可以看出有效值是6位,如果按四舍五入的话可以精确到8位,其中变量b贡献的有效数值只有2位。

          987654321

    +    987.654322

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

    987655308.654322    真实值

    987655296.000000    计算值

    987655296           二进制值,0x4E6B79B2

     

    对于这种数量级相差很大的计算,计算结果会保证高位数有效,数量级小的数相对计算结果显的太小了,不能按自身6位的精度保持,而是需要按照计算结果的6位精度保持。

     

    C语言中有关浮点数的定义

    C语言对浮点数做了一些规定,下面是摘自VisualC++ 2013头文件float.h中有关float型的定义,如下:

     

    [cpp] view plaincopyprint?

    1. #define FLT_DIG         6                       /* # of decimal digitsof precision */  
    2. #define FLT_EPSILON     1.192092896e-07F        /* smallest such that 1.0+FLT_EPSILON!= 1.0 */  
    3. #define FLT_GUARD       0  
    4. #define FLT_MANT_DIG    24                      /* # of bits in mantissa*/  
    5. #define FLT_MAX         3.402823466e+38F        /* max value */  
    6. #define FLT_MAX_10_EXP  38                      /* max decimal exponent*/  
    7. #define FLT_MAX_EXP     128                     /* max binary exponent */  
    8. #define FLT_MIN         1.175494351e-38F        /* min positive value */  
    9. #define FLT_MIN_10_EXP  (-37)                   /* min decimal exponent */  
    10. #define FLT_MIN_EXP     (-125)                  /* min binary exponent */  


     

     

    其中FLT_DIG定义了float型的十进制精度,是6位,与我们上面的讨论是一致的。

    FLT_EPSILON定义了float型在1.xx数量级下的最小精度,1.xx数量级下判断浮点数是否为0可以使用这个精度。

    FLT_MANT_DIG定义了float型F位的长度。

    FLT_MAX定义了float型可表示的最大数值。

    FLT_MAX_10_EXP定义了float型十进制的最大幂。

    FLT_MAX_EXP定义了float型二进制的最大幂。

    FLT_MIN定义了float型所能表示的最小正数。

    FLT_MIN_10_EXP定义了float型十进制的最小幂。

    FLT_MIN_EXP定义了float型二进制的最小幂。

     

    float.h文件里对其它的浮点数也做了规定,有兴趣的读者可以打开float.h头文件继续研究。

    展开全文
  • C语言浮点数运算

    2021-05-21 06:45:38
    C语言标准C89里规定了3种浮点数,float型、double型和long double型,常见的浮点型长度为float型占4个字节,double型占8个字节,long double型长度要大于等于double型,下面将以float型为例进行介绍,double型和long...

    C语言标准C89里规定了3种浮点数,float型、double型和long double型,常见的浮点型长度为float型占4个字节,double型占8个字节,long double型长度要大于等于double型,下面将以float型为例进行介绍,double型和long double型只是比float型位数长,原理是一样的 。

    float型可以表示的十进制范围是-3.402823466e38~3.402823466e38,而作为同为4个字节的定点数却只能表示-2147483648~2147483647的范围,使用同样的内存空间,浮点数却能比定点数表示大得多的范围,这是不是太神奇了?既然浮点数能表示这么大的范围,那么我们为何不使用浮点数来代替定点数呢?先不说浮点数实现起来比较复杂,有些处理器还专门配置了硬件浮点运算单元用于浮点运算,主要原因是浮点数根本就无法取代定点数,因为精度问题。鱼和熊掌不可兼得,浮点数表示了非常大的范围,但它失去了精度。

    ANSI/IEEE Std 754-1985标准

    IEEE 754是最广泛使用的二进制浮点数算术标准,被许多CPU与浮点运算器所采用。IEEE 754规定了多种表示浮点数值的方式,下面介绍32位二进制的float浮点类型。它被分为3个部分,分别是符号位S(sign bit)、指数偏差E(exponent bias)和小数部分F(fraction),这三部分都是对应二进制码的。

    36398750_1.jpg

    浮点表示的一般形式为(科学技术法规则):R=(S) * (1 + F) * 2e (R:实数       S:正负符号      F:小数部分     e:指数,不同于指数偏差)。

    符号位S:占1位,0代表浮点数是正数,1代表浮点数是负数。

    指数偏差E:占8位,范围是0~255,e = E - 127,e为正值表明转换成二进制码后,按科学计数法表达时向左移动了e位, 负值表明向右移动了e位。

    小数部分F:占23位,实际上是将浮点数转换成二进制码,再按科学计数法表达,将其小数部分存在F上,由于二进制码按科学计数法表达后,只要值不为0,整数部分就必然为1,所以可以省略整数部分。

    例如,3.75的二进制码为11.11,将该二进制码按科学计数法表达为1.111,则向左移动了1位,即e=1,E=e+127=128,F记录的便是小数部分,实际为111000...000。

    下面介绍一下小数部分转换为二进制码的方式。类似于整数的形式(如7 = 22 + 21 + 20),小数部分的转换形式为2-1、2-2、2-3、2-4......,例如0.5 = 2-1,即二进制码为0.1,0.05 = 2-5 + 2-6 + 2-9 + 2-10 + 2-13 + 2-14 +...... (可无限循环),即二进制码为0.00001100110011......。如果都以16位计,那么7的二进制码为0000000000000111,0.5的二进制码为0.1000000000000000,0.05的二进制码为0.0000110011001100。这是如何换算出来的呢?且看下面的算法便知:

    换算0.5,乘法结果初始为0.5,所有乘数为2,每次用乘法结果 * 乘数,得到新的乘法结果,结果中的整数部分被提取出来,剩余的小数部分继续参加下一次乘法运算,直到剩余小数部分为0,或者无终点(无限循环)。根据表格中的整数部分可知,二进制为0.1。

    整数部分

    乘数

    乘法结果

    剩余小数部分

    0.

    2

    0.5

    0.5

    1

    1

    0

    结束

    换算0.05,乘法结果初始为0.05,所有乘数为2,每次用乘法结果 * 乘数,得到新的乘法结果,结果中的整数部分被提取出来,剩余的小数部分继续参加下一次乘法运算,直到剩余小数部分为0,或者无终点(无限循环)。根据表格中的整数部分可知,二进制为0.00001100110011......。

    整数部分

    乘数

    乘法结果

    剩余小数部分

    0.

    2

    0.05

    0.05

    0

    2

    0.1

    0.1

    0

    2

    0.2

    0.2

    0

    2

    0.4

    0.4

    0

    2

    0.8

    0.8

    1

    2

    1.6

    0.6

    1

    2

    1.2

    0.2

    0

    2

    0.4

    0.4

    0

    2

    0.8

    0.8

    1

    2

    1.6

    0.6

    1

    1.2

    0.2

    无限循环

    例1:float型浮点数125.5转化成32位二进制浮点数。

    125.5的整数和小数部分的二进制码分别为1111101和0.1,于是125.5的二进制码为1111101.1,按科学技术法写为1.1111011*26,即向左移6位,则e=6,E=e+127=133,133的二进制码为10000101。而1.1111011把整数部分的1去掉后,剩下小数部分为1111011,之后补0至23位,构成F。所以125.5的32位二进制浮点数为:

    0  10000101  11110110000000000000000

    例2:float型浮点数0.5转化成32位二进制浮点数。

    类似的,0.5的二进制码为0.1,按科学技术法写为1.0*2-1,即向右移1位,则e=-1,则E=e+127=126,126的二进制码为01111110。而1.0把整数部分的1去掉后,剩下小数部分为0,之后补0至23位,构成F。所以0.5的32位二进制浮点数为:

    0  01111110  00000000000000000000000

    几个特殊的情形

    E=0,F=0时,表示浮点数0,此时浮点数受S影响,表现出+0和-0两种0,但数值是相等的。比如二进制数0x00000000表示+0,二进制数0x80000000表示-0。

    E=0,F不等于0时,浮点数为(S) * (F) * 2e,注意e为-126,而不是0-127=-127,而且F是0.xxx格式而不是1.xxx格式,比如0x00000001的浮点数为2-126*2-23=1.4012984643248170709237295832899e-45,而不是20-127*(1+2-23)。E从0变为1,不会产生增加2倍的关系,而是计算公式改变了(恢复正常公式)。

    E=255,F不等于0时,表示非数值,也就是说是非法数,例如0x7F800001。

    E=255,F=0时,表示无穷大的数,此时浮点数受S影响,例如0x7F800000表示正无穷大,0xFF800000表示负无穷大。做除法时,如果除以0时,结果将被记作0x7F800000。

    浮点数的精度

    从前文中可以看到,1.xxx这类浮点数中,F部分最小的是2-23,对应的十进制数为1.00000011920928955078125,可以精确表示到小数点后23位,但是一些C语言书上却说float型的有效位只有6~7位,这是为什么呢?原因在于二进制小数与十进制小数没有完全一一对应的关系,二进制小数相比十进制小数来说,是离散而不是连续的,我们来看看下面这些数字:

    二进制小数    十进制小数

    2-23       1.00000011920928955078125

    2-22       1.0000002384185791015625

    2-21       1.000000476837158203125

    2-20       1.00000095367431640625

    2-19       1.0000019073486328125

    2-18       1.000003814697265625

    这里只需要关注F,上面列出了1.xxx这类浮点数中的6个最小的二进制小数,及其对应的十进制数。可以看到使用二进制所能表示的最小小数是1.00000011920928955078125,其次是1.0000002384185791015625,这两个数之间是有间隔的,如果想用二进制小数来表示8位有效数(只算小数部分,小数点前面的1是隐藏的默认值)1.00000002、1.00000003、1.00000004......这些数是无法办到的,而7位有效数1.0000001可以用2-23来表示,1.0000002可以用2-22来表示,1.0000003可以用2-23+2-22来表示。从这个角度来看,float型所能精确表示的位数只有7位,7位之后的数虽然也是精确表示的,但却无法表示任意一个想表示的数值。

    但还是有一些例外的,比如说7位有效数1.0000006这个数就无法用F表示,这也表明二进制小数对于十进制小数来说相当于是离散的,刚好凑不出1.0000006这个数,从这点来看float型所能精确表示的位数只有6位。因此float型的有效位数是6~7位,但这个说法应该不是非常准确,准确来说应该是6位,C语言的头文件中规定也是6位。对于一个很大的数,例如1234567890,它是由于指数E系数而被放大了的,但它的有效位仍然是F所能表示的6~7位有效数字。1234567890用float表示后为1234567936,只有高7位是有效位,后3位是无效的。int型可以准确的表示1234567890,而float浮点数则只能近似的表示1234567890,精度问题决定了float型无法取代int型。

    展开全文
  • C语言浮点数运算,讲述原理并总结一些案例

    万次阅读 多人点赞 2014-03-04 16:07:24
    有些C语言书上说float型的有效位数是6~7位,为什么不是6位或者7位?而是一个变化的6~7位?  浮点数在内存中是如何存放的?  float浮点数要比同为4字节的int定点数表示的范围大的多,那么是否可以使用浮点数替代...
    有些C语言书上说float型的有效位数是6~7位,为什么不是6位或者7位?而是一个变化的6~7位?
            浮点数在内存中是如何存放的?
            float浮点数要比同为4字节的int定点数表示的范围大的多,那么是否可以使用浮点数替代定点数?
            为什么float型浮点数9.87654321 > 9.87654322不成立?为何10.2 - 9的结果不是1.2,而是1.1999998?为何987654321 + 987.654322的结果不是987655308.654322?
            如何才能精确比较浮点数真实的大小?
            看完本文档,你将会得到答案!

            在论坛上或QQ群中有时会看到新同学问一些有关浮点数运算的问题,经常是走了错误的方向,但苦于交流方式不方便,无法为其详细说明,在此,我将我所掌握的一些知识加以整理写出来,希望对大家能有所帮助。更多案例请访问我的博客blog.sina.com.cn/ifreecoding。
            我主要是从事底层软件开发的,最开始写驱动程序,后来也做一些简单的业务层软件,在我所涉及的工作范围内,我使用的都是定点数,而且90%以上都是无符号定点数,在我印象中并没有使用过浮点数,即使做过一个专门使用DSP来处理信号的项目,也只是使用了无符号定点数,我将在另一篇案例《C语言使用定点数代替浮点数计算》里介绍定点数处理简单的浮点数的方法,这也是在底层驱动中常使用的方法。

    C语言浮点数
                    C语言标准C89里规定了3种浮点数,float型、double型和long double型,其中float型占4个字节,double型占8个字节,long double型长度要大于等于double型,本文档将以float型为例进行介绍,double型和long double型只是比float型位数长,原理都是一样的。
            float型可以表示的范围是-3.402823466e38~3.402823466e38,而作为同为4个字节的定点数却只能表示-2147483648~2147483647的范围,使用同样的内存空间,浮点数却能比定点数表示大得多的范围,这是不是太神奇了?既然浮点数能表示这么大的范围,那么我们为何不使用浮点数来代替定点数呢?
            先不说浮点数实现起来比较复杂,有些处理器还专门配置了硬件浮点运算单元用于浮点运算,主要原因是浮点数根本就无法取代定点数,因为精度问题。鱼和熊掌不可兼得,浮点数表示了非常大的范围,但它失去了非常准的精度。在说明精度问题前,我们先了解一下浮点数的格式。
            
    ANSI/IEEE Std 754-1985标准
    IEEE 754是最广泛使用的二进制浮点数算术标准,被许多CPU与浮点运算器所采用。IEEE 754规定了多种表示浮点数值的方式,在本文档里只介绍32bits的float浮点类型。它被分为3个部分,分别是符号位S(sign bit)、指数偏差E(exponent bias)和小数部分F(fraction)。
    浮点数.JPG 

            其中S位占1bit,为bit31。S位为0代表浮点数是正数,S位为1代表浮点数是负数,比如说0x449A522C的S位为0,表示这是一个正数,0x849A522C的S位为1,表示这是一个负数。
            E位占8bits,为bit23~bit30。E位代表2的N次方,但需要减去127,比如说E位为87,那么E位的值为2(87-127)=9.094947017729282379150390625e-13。
            F位占23bits,为bit0~bit22。F位是小数点后面的位数,其中bit22是2-1=0.5,bit21是2-2=0.25,以此类推,bit0为2-23=0.00000011920928955078125。但F位里隐藏了一个1,也就是说F位所表示的值是1+(F位bit22~bit0所表示的数值),比如说F位是0b10100000000000000000001,只有bit22、bit20和bit0为1,那么F位的值为1+(2-1+2-3+2-23),为1.62500011920928955078125。
            综上所述,从二进制数换算到浮点数的公式为:(-1)S×2E-127×(1+F)。但还有几个特殊的情形:
    若E位为0并且F位也为0时表示浮点数0,此时浮点数受S位影响,表现出+0和-0两种0,但数值是相等的。比如二进制数0x00000000表示+0,二进制数0x80000000表示-0。
    若E位为0并且F位不为0时浮点数为(-1)S×2-126×F,注意,E位的指数是-126,而不是0-127=-127,而且F位是0.xx格式而不是1.xx格式,比如0x00000001的浮点数为2-126×2-23=1.4012984643248170709237295832899e-45,而不是20-121×(1+2-23)。一旦E为不为0,从0变为1,不是增加2倍的关系,因为公式改变了。
    若E位为255并且F位不为0时表示非数值,也就是说是非法数,例如0x7F800001。
    若E位为255并且F位为0时表示无穷大的数,此时浮点数受S位影响,例如0x7F800000表示正无穷大,0xFF800000表示负无穷大。当我们使用1个数除以0时,结果将被记作0x7F800000。
            
            浮点型在多个处理器间通信时,传递的数值是它的二进制数,比如说1234.5678这个浮点数的二进制数是0x449A522B,如果使用串口发送的话,就会发现串口里发送的是0x44、0x9A、0x52和0x2B这4个数(发送的顺序也可能是逆序,这与约定的字节序有关,与浮点格式无关),接收端接收到这4个数字后再组合成0x449A522B,按照IEEE 754的定义被解析成1234.5678,这样就实现浮点数通信了。如果两个处理器所使用的浮点数规则不同,则无法传递浮点数。
            
    浮点数的换算
            下面来看看浮点数与二进制数如何转换。
            
            例1,二进制数换算成浮点数:
            假如在内存中有一个二进制数为0x449A522C,先将十六进制转换成二进制,如下:
    0100  0100  1001  1010  0101  0010  0010  1100
            按照SEF的格式分段,如下:
    0  10001001  00110100101001000101100
            这个数值不是特殊的情形,可以按照公式(-1)S×2E-127×(1+F)转换。S位的值为(-1)0=1,E位的值为2137-127=1024。F位的值为1+2-3+2-4+2-6+2-9+2-11+2-14+2-18+2-20+2-21= 1.205632686614990234375。最终结果为1×1024×1.205632686614990234375= 1234.56787109375。
            其中F位比较长,使用二进制方式转换比较麻烦,也可以先转换成十六进制再计算,转换为十六进制如下:
    0011  0100  1010  0100  0101  1000
    0x3   0x4   0xA   0x4   0x5   0x8
            F位为23bits,需要在最后补一个0凑成24bits,共6个十六进制数。F位的值为1+3×16-1+4×16-2+10×16-3+4×16-4+5×16-5+8×16-6=1.205632686614990234375,与上面使用二进制方法得到的结果相同。
            
            例2,浮点数换算成二进制数:
            下面我们将-987.654e30换算成二进制数。我们先不看符号位,将987.654e30归一化为整数部分为1的形式,也就是写作987.654e30=2E-127×(1+F)的标准形式,其中E=log(987.654e30)/log2+127=109.6+127,取E位的整数值为109+127=236,再求F=987.654e30/2236-127-1=0.52172193,这个小数位数保留8位就够了,已经超出了7位的精度。然后我们求小数部分的二进制数,这个转换就没啥好说的了,依次减去2的幂,从2-1一直到2-23,够减的位置1,不够减的位置0,例如,2-1为0.5,0.52172193-0.5=0.02172193,F位的bit22置1,2-2为0.25,0.02172193不够减,F位的bit21置0,2-3为0.125,0.02172193不够减,F位的bit20置0,2-4为0.0625,0.02172193不够减,F位的bit19置0……,一直算到F位的bit0,这样就得到F位的数值。
            如果觉得使用二进制方式转换太麻烦的话也可以使用十六进制进行转换。16-1为0.0625,0.52172193/0.0625=8.3,说明够减8个,记做0x8,0.52172193-0.0625×8=0.02172193,16-2为0.00390625,0.02172193/0.00390625=5.6,说明够减5个,加上刚才的0x8记做0x85,以此类推:
            16的-N次幂        被减数        十六进制数        减后的数
    1        0.0625        0.52172193        0x8        0.02172193
    2        0.00390625        0.02172193        0x85        0.00219068
    3        0.000244140625        0.00219068        0x858        0.000237555
    4        0.0000152587890625        0.000237555        0x858F        0.0000086731640625
    5        0.00000095367431640625        0.0000086731640625        0x858F9        0.00000009009521484375
    6        0.000000059604644775390625        0.00000009009521484375        0x858F91        
            一直凑够23bits,也就是6个十六进制,得到0x858F91,换算成二进制如下所示:
    1000  0101  1000  1111  1001  0001
            由于只有23bits有效,因此需要去掉最后一个bit,二进制本着0舍1入的原则,变成
    1000  0101  1000  1111  1001  001
            最后需要再补上前面的S位和E位。由于是负数,S位为1。E位为236,二进制形式为1110 1100,将S、E、F位组合在一起就形成了:
    1  1110 1100  1000  0101  1000  1111  1001  001
            从左边最高位开始,4个一组合并成十六进制:
    1111  0110  0100  0010  1100  0111  1100  1001
            换算成十六进制为:
    0xF   0x6   0x4   0x2   0xC   0x7  0xC   0x9
            综上所述,-987.654e30换算成二进制数为0xF642C7C9。
            
    浮点数的精度
            在前面的讲解中可以看到1.xx这个数量级的最小数是2-23,对应的十进制数值为1.00000011920928955078125,可以精确表示到小数点后23位,但有些C语言书上却说float型的有效位只有6~7位,这是为什么?
            这是因为二进制小数与十进制小数没有完全一一对应的关系,二进制小数对于十进制小数来说相当于是离散的而不是连续的,我们来看看下面这些数字:
    二进制小数        十进制小数
    2-23        1.00000011920928955078125
    2-22        1.0000002384185791015625
    2-21        1.000000476837158203125
    2-20        1.00000095367431640625
    2-19        1.0000019073486328125
    2-18        1.000003814697265625
            不看S位和E位,只看F位,上表列出了1.xx这个数量级的6个最小幂的二进制小数,对应的十进制在上表的右边,可以看到使用二进制所能表示的最小小数是1.00000011920928955078125,接下来是1.0000002384185791015625,这两个数之间是有间隔的,如果想用二进制小数来表示8位有效数(只算小数部分,小数点前面的1是隐藏的默认值)1.00000002、1.00000003、1.00000004...这些数是无法办到的,而7位有效数1.0000001可以用2-23来表示,1.0000002可以用2-22来表示,1.0000003可以用2-23+2-22来表示。从这个角度来看,float型所能精确表示的位数只有7位,7位之后的数虽然也是精确表示的,但却无法表示任意一个想表示的数值。
            但还是有一些例外的,比如说7位有效数1.0000006这个数就无法使用F位表示,二进制小数对于十进制小数来说相当于是离散的,刚好凑不出1.0000006这个数,从这点来看float型所能精确表示的位数只有6位。至于5位有效值的任何数都是可以使用F位相加组合出来的,即便是乘以E位的指数后也是可以准确表示出来的。
            因此float型的有效位数是6~7位,但这个说法应该不是非常准确,准确来说应该是6位,C语言的头文件中规定也是6位。
            
            对于一个很大的数,比如说1234567890,它是F位乘上E位的系数被放大了的,但它的有效位仍然是F位所能表示的6位有效数字。1234567890对应的二进制数是0x4E932C06,其中F位的数值为1.1497809886932373046875,E位的数值为230=1073741824,1073741824×1.1497809886932373046875=1234567936,对比1234567890,也只有高7位是有效位,后3位是无效的。int型定点数可以准确的表示1234567890,而float浮点数则只能近似的表示1234567890,精度问题决定了float型根本无法取代int型。
            
    浮点数的比较
            从上面的讨论可以看出,float型的有效位数是6位,那么我们在用float型运算时就要注意了,来看下面这段程序:
    #include <stdio.h>

    int main(void)
    {
        float a = 9.87654321;
        float b = 9.87654322;
        
        if(a > b)
        {
            printf("a > b\n");
        }
        else if(a == b)
        {
            printf("a == b\n");
        }
        else
        {
            printf("a < b\n");
            }

        return 0;
    }
            按照我们平时的经验来说这段程序应该走a < b的分支,但程序运行的结果却走了a == b的分支,原因就是float型的精度问题,float型无法区分出小数点后的第8位数,在内存中,a和b的二进制数都是0x411E0652,因此就走了a == b的分支。
            某些编译器在编译时会发现a和b的值超出了浮点数的精度,会产生一个告警,提示数据超过精度,但有些编译器则不会做任何提示。最可怕的是有一类编译器,调试窗口里显示的长度超出float型的精度,比如说a的值显示为9.87654321,b的值显示为9.87654322,但在运行时,硬件可不管这套,硬件认为这2个数都是0x411E0652,因此实际运行结果是a == b的分支。以前就遇到过一个同学在QQ群里问一个类似的问题,在调试窗口里明明写着a是9.87654321,小于 b的9.87654322,但运行结果却是a ==b。当时我给他说了半天也没能让他明白这个问题,希望他能有机会看到这个文档,希望他这次能够明白^_^。
            
            由于精度这个问题的限制,我们在浮点数比较时就需要加一个可接受的精度条件来做判决,比如说上面的这个问题,如果我们认为精度在0.00001就足够了,那么a - b之差的绝对值只要小于0.00001,我们就认为a和b的值是相等的,大于0.00001则认为不等,还要考虑到a - b正负等情况,因此可以将上面的程序改写为:
    #include <stdio.h>

    int main(void)
    {
        float a = 9.87654321;
        float b = 9.87654322;

        if(a - b < -0.00001)
        {
            printf("a < b\n");
        }
        else if(a - b > 0.00001)
        {
            printf("a > b\n");
        }
        else
        {
            printf("a == b\n");
            }

        return 0;
    }
            例子中a和b之差的绝对值小于0.00001,因此认为a和b是相等的,运行程序,也正确的打印了a == b。

            也许你会觉得费了这么大的劲,最后2个程序运行的结果还是一样的,这不是画蛇添足么?硬件已经自动考虑到精度问题了,为什么我们还要去再做一个精度的限定?这是因为我们在应用中的精度往往要低于硬件的6位精度。比如说我们使用2个AD采集2V的电压,并对这2个电压值做比较,一般要求精确到0.1V即可。实际情况下AD采集出来的数值都会围绕真实值有上下的波动,比如说AD的精度是0.001V,我们采集出的电压数值就可能是2.003V、2.001V、1.999V等围绕2V波动的数值,如果我们在程序里不对精度加以限制就对这些数值做比较,就会发现几乎每次采集到的电压值都是不同的。在这种情况下我们就需要将精度设定为0.1V,将上面例子中的0.00001改为0.1,就可以正确的判断出每次采集的电压值都是相同的了。
            在实际使用AD采样时可能并不需要使用浮点数,我一般都是使用定点数来代替浮点数进行处理的,请参考另一篇案例《C语言使用定点数代替浮点数计算》。

            下面我们再看一个例子:
    #include <stdio.h>

    int main(void)
    {
        float a = 987654321;
        float b = 987654322;

        if(a - b < -0.00001)
        {
            printf("a < b\n");
        }
        else if(a - b > 0.00001)
        {
            printf("a > b\n");
        }
        else
        {
            printf("a == b\n");
            }

        return 0;
    }
            这个例子中的两个数都是很大的数,已经远远超过了0.00001的精度,运行结果是不是应该是a < b?但程序运行的结果依然是a == b。这是因为这个例子里的a和b并不是1.xx的数量级,我们将a和b进行归一化,都除以900000000就会发现a = 1.09739369,b = 1.097393691,只是在第9位才出现不同,因此在0.00001这个精度下,这2个数还是相等的。换个角度来看,a和b虽然是很大的数了,但F位仅能表示23bits,有效值仅有6位,a和b的大是因为E位的指数放大F位表现出来的,但有效值依然是6位。在内存中a和b的二进制数都是0x4E6B79A3。其中E位为156,2156-127=536870912,F位为0xD6F346,F位1.xx数量级的1.83964955806732177734375被E位放大了536870912倍,E位如果算7位有效精度的话能精确到0.0000001,乘以536870912已经被放大了53倍多,这就说明a和b的个位与十位已经不是有效位数了,所以程序运行的结果表现出a == b也是正常的。
            由此可见,设定一个合理的精度是需要结合浮点数的数量级的,这看起来似乎比较难,毕竟在程序运行时十分精确的跟踪每个浮点数的数量级并不容易实现,但实际应用中需要比较的浮点数往往都会有其物理含义,例如上面电压的例子,因此,根据浮点数的物理含义,浮点数的精度还是比较好确定的。当然在一些复杂的数值运算过程中可能会存在非常复杂的情况,这时浮点数的精度问题就比较棘手了,所幸我所做的都是比较简单的东西,那些复杂的情况就不讨论了,我也没能力讨论^_^。
            上面所说的都是同等数量级下的浮点数进行比较的情况,不同数量级的浮点数比较则没有这个限制,比如说1.23456789与12.23456789的比较,在E位已经足以区分大小了,因此F位的精度就没有必要再比较了。
            
    浮点数的加减
            二进制小数与十进制小数之间不存在一一对应的关系,因此某些十进制很整的加减法小数运算由二进制小数来实现就表现出了不整的情况,来看下面的例子:
    #include <stdio.h>

    int main(void)
    {
        float a = 10.2;
        float b = 9;
        float c;

        c = a - b;

            printf("%f\n", c);

        return 0;
    }
            如果用十进制计算的话变量c应该为1.2,在Visual C++ 2010环境下实验输出为1.200000,但实际上c变量的值是1.1999998,只不过是在输出时被四舍五入为1.200000罢了。在内存中c变量的二进制数是0x3F999998,它对应的浮点数是0.19999980926513671875。如果我们将printf函数%f的格式改为%.7f格式,就会看到c变量输出的值是1.1999998。
            
            两个数量级相差很大的数做加减运算时,数值小的浮点数会受精度限制而被忽略,看下面的例子:
    #include <stdio.h>

    int main(void)
    {
        float a = 987654321;
        float b = 987.654322;
        float c;

        c = a + b;

        printf("%f\n", c);

        return 0;
    }
            Visual C++ 2010上的计算结果为987655296.000000,而实际的真实值为987655308.654322,二进制值为0x4E6B79B2对应987655296,就是987655296.000000。可以看出有效值是6位,如果按四舍五入的话可以精确到8位,其中变量b贡献的有效数值只有2位。
    987654321
    +     987.654322
    ----------------
    987655308.654322    真实值
    987655296.000000    计算值
    987655296           二进制值,0x4E6B79B2
            对于这种数量级相差很大的计算,计算结果会保证高位数有效,数量级小的数相对计算结果显的太小了,不能按自身6位的精度保持,而是需要按照计算结果的6位精度保持。

    使用二进制数比较浮点数
            下面我们从另一个方向探索一下浮点数的比较问题。
            我们可以使用(-1)S×2E-127×(1+F)这个公式来计算IEEE 754标准规定的浮点数,先抛开S位和那4种特殊的规定,只看E位和F位2E-127×(1+F),我们会发现E位和F位组成的数值具有单调递增性,也就是说任意一个浮点数A掩掉S位的数值B = (A & 0x7FFFFFFF)是单调递增的,如果A1大于A2,那么B1一定大于B2,反之亦然,如果B1大于B2,那么A1也一定大于A2,这样的话我们就可以使用浮点数的二进制数来比较大小了。
            看下面程序,使用联合体将浮点数转换成二进制数再做比较:
    #include <stdio.h>

    typedef union float_int
    {
        float f;
        int i;
    }FLOAT_INT;

    int main(void)
    {
        FLOAT_INT a;
        FLOAT_INT b;
        int ca;
        int cb;

        a.f = 9.87654321;
        b.f = 9.87654322;

        ca = a.i & 0x7FFFFFFF;
        cb = b.i & 0x7FFFFFFF;

        if(ca > cb)
        {
            printf("a > b\n");
        }
        else if(ca == cb)
        {
            printf("a == b\n");
        }
        else
        {
            printf("a < b\n");
        }

        return 0;
    }
            上面的程序使用联合体使浮点型和整型共享同一个内存空间,浮点型变量.f输入浮点数,使用整型变量.i就可以获取到.f的二进制数,比较时利用.i的E位和F位就可以判断浮点数的绝对大小了,这个判决的精度为硬件所支持的精度。
            
            如果考虑到S位,情况会有些变化。S位是符号位,0正1负,与int型的符号位有一样的作用,并且都在bit31。从这点来看,不对浮点数的二进制数进行(& 0x7FFFFFFF)的操作而是直接使用浮点数的二进制数来当做int型数做比较,那么浮点数的S位则正好可以充当int型数的符号位。两个比较的浮点数都是正数的情况就不用说了,上面的例子(& 0x7FFFFFFF)已经验证了。正浮点数与负浮点数比较的情况也没有问题,浮点数和int型数的符号位是兼容的,符号位就可以直接比较出大小,比如说-9.87654321和9.87654322之间做比较,-9.87654321的bit31是1,9.87654322的bit31是0,从二进制int型数的角度来看,bit31为0是正数,bit31为1是负数,通过符号位就可以直接判断出大小。最后剩下两个负浮点数比较的情况了,这种情况存在问题,如果采用二进制int型数来比较浮点数的话,结果则正好相反,比如说-1.5和-1.25做比较,int型数是用补码表示的,对于两个负数来说,补码的二进制数值越大则补码值也越大。-1.5的补码是0xBFC00000,-1.25的补码是0xBFA00000,从二进制角度来看0xBFC00000>0xBFA00000,因此int的补码是0xBFC00000>0xBFA00000,也就是-1077936128>-1080033280,如果使用int型来判断,就会得出-1.5 > -1.25的结论,正好相反了。这样的话我们就需要对两个浮点数的符号位做一个判断,如果同为负数的话则需要将比较结果反一下,如下面程序:
    #include <stdio.h>

    typedef union float_int
    {
        float f;
        int i;
    }FLOAT_INT;

    int main(void)
    {
        FLOAT_INT a;
        FLOAT_INT b;

        a.f = -9.876543;
        b.f = -9.876542;

        if((a.i < 0) && (b.i < 0))
        {
            if(a.i < b.i)
            {
                printf("a > b\n");
            }
            else if(a.i == b.i)
            {
                printf("a == b\n");
            }
            else
            {
                printf("a < b\n");
            }
        }
        else
        {
            if(a.i > b.i)
            {
                printf("a > b\n");
            }
            else if(a.i == b.i)
            {
                printf("a == b\n");
            }
            else
            {
                printf("a < b\n");
            }
        }
    }
            如果再考虑IEEE 754标准定义的那几种特殊情况,问题变得又会复杂一些,比如说在运算过程中有±x / 0的情况出现,那么结果就是一个±无穷大的数,还有可能遇到±0等情况,这些问题在这里就不讨论,只要增加相应的条件分支就可以做出判断的。
            
            使用二进制数比较浮点数的方法可以依据硬件精度判断出浮点数的真正大小,但实际使用过程中往往不是根据硬件精度做判断的,因此最好还是使用上面所介绍的加入精度的判断方法。
            
    C语言中有关浮点数的定义
            C语言对浮点数做了一些规定,下面是摘自Visual C++ 2010头文件float.h中有关float型的定义,如下:
    #define FLT_DIG         6                       /* # of decimal digits of precision */
    #define FLT_EPSILON     1.192092896e-07F        /* smallest such that 1.0+FLT_EPSILON != 1.0 */
    #define FLT_GUARD       0
    #define FLT_MANT_DIG    24                      /* # of bits in mantissa */
    #define FLT_MAX         3.402823466e+38F        /* max value */
    #define FLT_MAX_10_EXP  38                      /* max decimal exponent */
    #define FLT_MAX_EXP     128                     /* max binary exponent */
    #define FLT_MIN         1.175494351e-38F        /* min positive value */
    #define FLT_MIN_10_EXP  (-37)                   /* min decimal exponent */
    #define FLT_MIN_EXP     (-125)                  /* min binary exponent */
            其中FLT_DIG定义了float型的十进制精度,是6位,与我们上面的讨论是一致的。
            FLT_EPSILON定义了float型在1.xx数量级下的最小精度,1.xx数量级下判断浮点数是否为0可以使用这个精度。
            FLT_GUARD不知道是啥意思--!
            FLT_MANT_DIG定义了float型F位的长度。
            FLT_MAX定义了float型可表示的最大数值。
            FLT_MAX_10_EXP定义了float型十进制的最大幂。
            FLT_MAX_EXP定义了float型二进制的最大幂。
            FLT_MIN定义了float型所能表示的最小正数。
            FLT_MIN_10_EXP定义了float型十进制的最小幂。
            FLT_MIN_EXP定义了float型二进制的最小幂。

            float.h文件里对其它的浮点数也做了规定,本文不再做介绍了。
    展开全文
  • 看一小段C语言程序: int main() { float x = 1.3; x = x - (int)x; int i = (int)(x*10); return 0; } 在你心目中, 变量 I 是怎样的结果? 如果你理所当然地认为是3的话, 那么你就错了~~~ 实际结果...
  • 文章目录一个小例题浮点数在内存的存储规则浮点数的分解形式规定浮点数在内存表示形式回到第一个题的解释 一个小例题 ????请问下面输出的结果是什么? 得出结果看看和自己预期的是否一样。 int main() { int n = 9; ...
  •  有些C语言书上说float型的有效位数是6~7位,为什么不是6位或者7位?而是一个变化的6~7位?  浮点数在内存中是如何存放的?  float浮点数要比同为4字节的int定点数表示的范围大的多,那么是否可以使用浮点数...
  • c语言浮点数和整数转换 C中的数据类型 (Data Types in C) There are several different ways to store data in C, and they are all unique from each other. The types of data that information can be stored ...
  • C语言中,不能使用浮点数进行==和!=运算,比如以下代码: #include<stdio.h> int main() { float a = 3.14; if (a == 3.14) { printf("%f",a); } return 0; } 运行后会报错,如下: 二、原因 ...
  • ![图片说明](https://img-ask.csdn.net/upload/201907/31/1564539265_378353.jpg) 就像图片中这样,换成float就没有问题了。不知道什么原因
  • C语言浮点数解惑

    千次阅读 多人点赞 2013-08-12 19:15:49
     有些C语言书上说float型的有效位数是6~7位,为什么不是6位或者7位?而是一个变化的6~7位?  浮点数在内存中是如何存放的?  float浮点数要比同为4字节的int定点数表示的范围大的多,那么是否可以使用浮点数替代...
  • c语言两个浮点数相加As we know that modules also known as the remainder of the two numbers can be found using the modulus (%) operator which is an arithmetic operator in C/C++. The modules operator ...
  • 本文详解了浮点数float和double在内存中的存储方式
  • C语言标准C89里规定了3种浮点数,float型、double型和long double型,常见的浮点型长度为float型占4个字节,double型占8个字节,long double型长度要大于等于double型,下面将以float型为例进行介绍,double型和long...
  • 如何判断浮点数的有效性(C语言)

    千次阅读 2020-02-28 22:37:48
    文章目录一、问题描述1、什么情况下计算结果为inf2、什么情况下计算结果为nan二、判断浮点数的有效性二、推导步骤三、CCC代码四、总结五、参考文献/资料 一、问题描述   在编程过程中,有时候会因为考虑不周或者...
  • 因而,它要识别并检测 6 种类型的异常条件:无效操作(#I)除零(#Z)非规格化操作数(#D)数字上溢(#O)数字下溢(#U)模糊精度(#P)前三个(#I、#Z 和 #D)在全部运算操作发生前进行检测,后三个(#O、#U 和 #P)则在操作发生后...
  • 1. 背景知识IEEE754是由IEEE制定的有关浮点数的工业标准。针对于单精度浮点数,其公式如下,S为符号位,只占1位,为0表示正数,为1表示负数。P为指数(阶码),用移码表示,占8位。M为尾数,用原码表示,占23位。X = ...
  • 需检测异常 (1)2/0.1= ----7 (2)2/0.1四则 ----2 (3)(2/0.1) ----4 以上三种情况检测sfs, 当sfs == 1,数出栈,当前输入无效,sfs=0,state=2 当sfs==0, 有效 三.开发工具 keil uv4(uv3缺少浮点数的库) 四.遇到的问题...
  • C语言的本质(4)——浮点数的本质与运算 C语言规定了3种浮点数,float型、double型和long double型,其中float型占4个字节,double型占8个字节,longdouble型长度要大于等于double型,本文档将以float型为例...
  • C语言复习

    2021-05-20 18:34:52
    输出和输入 printf() 常用的字符串格式符: 1 2 3 4 5 6 7 8 9十进制整型 : %d 八进制 : %o 十六进制: %x/%X 字符 : %c 字符串: %s 双精度浮点数: %lf 输出内存地址: %p %hd用来输出 short int 类型 %ld用来输出 ...
  • C语言中常用的几种基本数据类型有基本数据类型的长度short =2 unsigned short=2char =1 unsigned char=1int =4 unsigned int=4long =8 unsigned long=8float=4float没有unsigneddouble=8double没有unsigned占位符的...
  • C语言中规定使用定点数格式来存储short、int、long类型的整数,使用浮点数格式来存储float、double类型的小数。整数和小数在内存中存储的格式不一样。 我们通常认为浮点数和小数是等价的,并没有
  • C语言实型数据(浮点数

    千次阅读 2015-01-13 18:29:48
    实型数据也称为浮点数或实数。在C语言中,实数只采用十进制。它有二种形式:十进制小数形式和指数形式。 实数的表示 1) 十进制数形式 由数码0~ 9和小数点组成。 例如:0.0、25.0、5.789、0.13、5.0、300.、-267....
  • c浮点数运算

    千次阅读 2017-09-25 22:24:47
     有些C语言书上说float型的有效位数是6~7位,为什么不是6位或者7位?而是一个变化的6~7位? 浮点数在内存中是如何存放的? float浮点数要比同为4字节的int定点数表示的范围大的多,那么是否可以使用浮点数...
  • 能识别浮点数的简单语法分析程序(C语言实现)此语法分析程序的基本词法分析能够识别基本字、标识符、有符号整数、有符号浮点数、运算符和界符)。语法结构定义::= { +|-}::= {*|/}::=ID|num|()num::= ( +|-|ε ) 数字...
  • // #2 打印输出: *pf=10.000000 -> *fpn=10.000000 *fpn=10.000000 -> *pf=10.000000 因为fpn这个指针变量声明为float类型,它存储的地址是无效的。其实无论怎样,都应该类型正确的匹配! 从上面可以看到,原来的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,764
精华内容 2,305
关键字:

c语言浮点数无效