1 背景一款流量计量产品,涉及到浮点数的累加,由于当时对浮点数的格式没有深入了解,想当然的就直接累加了程序编制完毕,在调试时发现下列问题:瞬时量测量精度高于指标要求,累积量在程序运行的开始阶段精度也满足要求,但随着测量时间的增长,累积量的实际测量值和理论计算值之间的差别越来越大,超过了技术指标的要求若不加干涉任其运行,当时间足够长时,显示模块显示的数据不再发生变化,即流量计的累积量不再发生变化了,但此时瞬时量显示的数据依旧正确,下面我们就来聊聊关于嵌入式浮点计算测试程序?接下来我们就一起去了解一下吧!

嵌入式浮点计算测试程序(嵌入式系统中浮点数累加问题)

嵌入式浮点计算测试程序

1 背景

一款流量计量产品,涉及到浮点数的累加,由于当时对浮点数的格式没有深入了解,想当然的就直接累加了。程序编制完毕,在调试时发现下列问题:瞬时量测量精度高于指标要求,累积量在程序运行的开始阶段精度也满足要求,但随着测量时间的增长,累积量的实际测量值和理论计算值之间的差别越来越大,超过了技术指标的要求。若不加干涉任其运行,当时间足够长时,显示模块显示的数据不再发生变化,即流量计的累积量不再发生变化了,但此时瞬时量显示的数据依旧正确。

表1 累积量测试结果

序号

累加初值

累加终值

实际累加值

误差

1

321

920

599

-0.17%

2

932

1524

592

-1.33%

3

1633

2223

590

-1.67%

4

2956

3537

581

-3.17%

某次测试时,通过设置流量计参数,使瞬时量理论值3600t/h,以10min为一个测量周期进行测量,理论计算得累积量每个周期应累加600。实测数据如表1所列。由表1中的数据可知,第1个测试周期精度满足要求,从第2个周期开始误差已经超过了技术指标要求,并且误差随着时间的增加而增大,在这种状态下流量积算仪是无法正常工作的。为什么会出现这种累加误差呢?要解决这个问题我们就有必要先了解一下浮点数的定义及在内存中存储格式。

2 浮点数的存储格式

实数r的二进制科学计数法可表示为r=(- 1)S×2E× M, 其中 S ( Sign)为实数的符号, “0”正 “1”负。E ( Exponent ) 为二进制数的指数, M( Mantissa)表示尾数,规范的二进制数 M = 1. d1d2 …dN C 语言的浮点数类型Float和Double采纳了IEEE 754标准中所定义的单精度32位浮点数和双精度64位浮点数的格式。具体的格式参见表 2和表3。

表中的指数域采用移码方式存储,单精度要指数值加上偏移量127,双精度要指数值加上偏移量1023。比如,单精度的实际指数值0在指数域中将保存为127;而保存在指数域中的64则表示实际的指数值-63。对于单精度数,可以表达的指数值的范围在-127~ 128 之间(包含两端) 。实际的指数值-127(保存为全0)以及 128(保存为全1)保留用作特殊值的处理。这样,实际可以表达的有效指数范围就在-126~ 127之间。

表中的第三个域为尾数域,其中单精度数为23位长,双精度数为52位长。除了某些特殊值外,IEEE 标准要求浮点数必须是规范的。这意味着尾数的小数点左侧必须为1,因此在保存尾数的时候,可以省略小数点前面这个1,从而腾出一个二进制位来保存更多的尾数。这样实际上用23位长的尾数域表达了24位的尾数。

表2 单精度浮点数

符号

指数

尾数

1bit

8bit

23bit

表3 双精度浮点数

符号

指数

尾数

1bit

11bit

52bit

值得注意的是,对于单精度数,由于我们只有24位的尾数(其中一位隐藏) ,所以可以表达的最大尾数为224-1= 16777215。由此,我们可以看到单精度的浮点数可以表达的十进制数值中,真正有效的数字不高于8 位。 Float型的一些特殊约定:

1) 当E= 0,M= 0时,表示0;

2) 当E = 0,M!= 0时,表示非规范化数,即r= (-1) S×2-127×( 0. M) ;

3) 当E = 255,M= 0时,表示无穷大,用符号位来确定是正无穷大还是负无穷大;

4) 当E= 255, M! = 0时,表示 N aN (No t a Number,不是一个数);

对于Double型,也有相似的约定。

3 浮点数累加误差问题解决

通过对累积量测试结果的分析我们找到了产生误差的原因:程序中在计算累积量SUM时执行的是SUN=SUM Instant,而参考文献明确告诉我们,两个浮点数相加时,其误差随着两个数差别的增大而增大。表1中误差的变化规律也证明了这一点。这是因为每次执行语句“SUM=SUM Instant;”时,Instant是不变的,而SUM在不断增加。当两者大小相差的数量级足够大时,Instant与SUM相加会丢失Instant,致使流量计累积量读数不再发生变化,这也是前面调试时出现问题的原因。

为什么两个相差很大的浮点数相加会误差很大呢?通过上面对浮点数存储格式的分析可以知道,由于Float 型变量是用有限的存储单元存储的,因此能提供的有效数字总是有限的,在有效位以外的数字将被舍去,由此可能会产生一些误差。

例如:

Main( )

{ float a, b;

a=123456.789e5;

b=a 20.0f;

printf(“a=%f\n b=%f\n”,a ,b) ; }

运行结果:

a=12345678848.000000

b=12345678848.000000

输出结果是a与b相等。但理论上b应比a大20,应为12345678920。而Float型变量只能保证7位有效数字。后面的数字是无意义的,并不准确地表示该数。因此应该避免将一个很大的数和一个很小的数直接相加或相减,否则就会“丢失”小的数。

知道了以上原因,对于累积量的累加我们就有了应对措施。在程序中将累积量的变量分为整数部分和小数部分分别定义,即定义两个float型变量INT_SUM和FP_SUM。INT_SUM中只保存累积量的整数部分,而FP_SUM中保存累积量的小数部分。在进行流量的累积计算时执行“FP_SUM=FP_SUM Instant;”当FP_SUM中的值大于等1.0时,INT_SUM=INT_SUM 1,同时FP_SUM=FP_SUM-1.0,这样FP_SUM中的值就始终是小于1的小数。因为INT_SUM=INT_SUM 1始终执行的是整数操作,不存在精度问题,而执行“FP_SUM=FP_SUM Instant;”时FP_SUM是小于1的小数部分,Instan每秒也是小于等于1(最大量程是3600t/h)的数据,二者相加精度也是满足要求的。最终的累积量等于INT_SUM 与FP_SUM的和,显示时先求二者的和再进行显示。虽然在求INT_SUM 与FP_SUM的和时也存在误差,但误差不是累积的,不会随时间的推移而增大,并且FP_SUM只是小于1的小数部分,最终误差可以忽略不计。

采用上述方法对累积量进行处理后,很好地解决了测量精度随时间增加而变大的问题,最终使计量精度达到了供需双方都满意的范围。

4 C语言中浮点数使用时的几点建议

通过以上对C语言中浮点数存储格式的及实际应用中的问题分析,可以得出一下结论:

(1)避免将一个很大的浮点数和一个很小的浮点数数直接相加或相减

因为这样做可能会丢失小的数。特别是在累加计算时,由于初始时,基数很小可能看不出问题,但随着时间的推移,基数的增大,问题就出来了。这给程序的正常运行埋下了隐患。

(2)避免对两个浮点数直接做是否相等的判断

由于浮点数在内存中的存储误差,因此可能出现这样的情况:在理论上应该相等的两个数,用计算机却判断它们却为不相等。如果想判断x是否等于y ,应改写为:fabs(x-y)<10-6,或小于一个其它足够小的数。只要小于此数,就认为x和y 足够地接近,近似地认为相等。如果x和y的值比较大(如约等于1030) ,则x-y的差可能大于 10-5,因此可用相对误差,即fabs((x-y)/x)<10-5,当此关系表达式的值为真时,x和y的相对误差小于百万分之一。

(3)慎用浮点数作为循环变量

C语言中的循环变量可以用浮点数,但是使用浮点数作为循环变量一定要仔细考虑,由于浮点数的误差,可能会使循环次数达不到预定的次数而导致程序出现逻辑错误。

,