仪表设计中的浮点数问题及解决办法
2010-03-20张延宇李伟
张延宇,李伟
(河南大学计算机与信息工程学院,开封475004)
引 言
C语言以其编程效率高、代码可移植性好、程序易于维护等特点,在仪表系统及其他嵌入式系统开发中应用十分广泛。尤其在处理浮点数的运算过程中,C语言与汇编语言相比其优势更加明显。因此,C语言深得项目开发人员的青睐。但是在浮点数处理过程中,若处理不当,则会在系统调试过程中出现异常,致使系统无法工作。
本文以笔者开发的某型号流量计为背景,讨论了在仪表设计过程中应用C语言处理浮点数时出现的问题及相应的解决办法,以供读者在遇到类似的问题时参考。流量计的MCU为Microchip公司的PIC16F876A,集成开发环境是Microchip公司的MPLAB8.3,C编译器选用HITECH公司的PICC9.5。
1 精度问题
项目要求实时计算累积流量并刷新数据显示,同时为防止掉电时累积量丢失,要求系统定时刷新EEPROM中累积量的值。为此,软件设计时设置两个变量Cumulation和Instant,分别用来存储累计量和瞬时量。Instant根据相应的计算公式计算得到,Instant每秒累加便得累积量Cumulation。累积量的数据显示范围为0~99 999 999,至上限值后回零,重现从零显示,要求满量程内累积精度优于0.3%。瞬时量的数据显示范围为0.000 1~99 999,需实时刷新,测量精度优于0.5%.
程序编制完毕,在调试时发现下列问题:瞬时量测量精度高于指标要求,累积量在程序运行的开始阶段精度也满足要求,但随着测量时间的增长,累积量的实际测量值和理论计算值之间的差别越来越大,超过了技术指标的要求。若不加干涉任其运行,当时间足够长时,显示模块显示的数据不再发生变化,即流量计的累积量不再发生变化了,但此时瞬时量显示的数据依旧正确。某次测试时,通过设置流量计参数,使瞬时量理论值为3600 m3/h,以10 min为一个测量周期进行测量,理论计算得累积量每个周期应累加600。实测数据如表1所列。
表1 累积量测试结果(1)
由表1中的数据可知,第1个测试周期精度满足要求,从第2个周期开始误差已经超过了技术指标要求,并且误差随着时间的增加而增大,在这种状态下流量计是无法正常工作的。那么问题出现在什么地方呢?经过查阅资料和仔细研读程序发现,在定义累积量和瞬时量时采用了如下形式:
使用了编译器PICC 9.5的默认编译设置。查阅编译器的使用手册得知,H I-TECH公司的编译器PICC 9.5的浮点数采用IEEE754规范,一个float类型数据占24位,同时支持以24位、32位两种方式存储一个double型数据,但为了节约存储空间,在不对编译器选项修改的情况下,double型数据采用的也是24位[1]。显然,在此默认情况下累积量的计算精度不能满足要求。
通过修改编译器选项,使double型数据以32位格式存储,同时修改程序的其他相关地方后,重新进行测试。测试时通过设置流量计参数,使瞬时流量理论值为3600 m3/h,以1个小时为一测量周期,显然累积量每小时的累积值理论上应为3600。实测数据如表2所列,测试开始时刻Cumu lation等于50。
表2 累积量测试结果(2)
比较表1和表2的数据可知,修改效果十分明显,在同等的测量条件下仪表连续运行27小时后,累积量的测量精度仍然满足技术指标要求。但存在的问题也很明显,通过对比数据发现,累积量的测量误差是随着时间的增加而增加的。可以预见,当运行的时间足够长时测量误差最终会突破技术指标的要求,实验结果确实也证明了这一点。
经过研究发现,问题出现在处理累积量的方法上。在前面的程序中直接通过每秒执行一次语句“Cumulation=Cumulation+Instant;”来计算累积量。而参考文献[2]明确告诉我们,两个浮点数相加时,其误差随着两个数差别的增大而增大,表2中误差的变化规律也证明了这一点。这是因为每次执行语句“Cumulation=Cumulation+Instant;”时,Instant是不变的,而Cumulation在不断增加。当两者大小相差的数量级足够大时,Instant与Cumulation相加会丢失Instant,致使流量计累积量读数不再发生变化,这也是前面调试时出现问题的原因。
这个问题可以应用汇编编程的处理方法来解决,但此种方法比较繁琐,编程比较困难。本文给出了一种新的处理方法。上述大数加小数丢失小数的情况在整数相加时是不存在的,同时,流量计在运行时当测量的瞬时流量小于下限流量时会当作干扰信号切除掉,累积量无需累加。实验数据表明,当瞬时流量在下限流量和上限流量限定的全量程范围内变化时,累积量小于4000时测量精度完全满足技术指标的要求。我们采用的办法是,将累积量分两部分来存储。为此,另定义一个unsigned long型变量HBCumu,每当Cumulation累加超过2000时执行下述操作:
HBCumu=HBCumu+2000;
Cumulation=Cumulation-2000;
即每次将Cumulation中的2000转存到HBCumu中,由于计算HBCumu时采用的是整数相加,因此不存在精度问题。同时通过此种操作,可以把Cumulation和Instant的大小差别限定在一个合适的范围内,从而保证计算Cumulation时精度满足要求。在EEPROM中分别给HBCumu、Cumulation开辟有独立的存储单元,每个存储周期分别存储两者的值。当前累积量的真实值等于HBCumu和Cumulation的和,显示数据时先求两者的和再进行显示。采用新方法后,在与前述相同的测试条件下重新对流量计进行测试,实测数据如表3所列。测试开始时刻,流量计的初始读数为86。
表3 累积量测试结果(3)
比较表3和表2的数据可以看出,采用转存方法对累积量进行存储后,很好地解决了测量精度随时间增加而变差的问题。我们对累积量计数满99 999 999的前后阶段也进行了测试,实验结果表明,测量误差与表3中的误差一致,这表明在全量程范围内误差都稳定在一个令人满意的范围内。此种方法与利用汇编编程处理的方法相比,避免了利用汇编代码处理浮点数的繁琐工作,保持了C语言的高效性和代码的良好移植性。
2 存储问题
项目要求在流量计正常运行时能够随时设定液体的密度、流量系数等参数,并且这些参数小数点后数据的位数固定。比如密度设定值,数据设定范围为0.001~999.999,小数点后固定为3位数据。程序编制完毕在调试阶段发现的问题是,当把参数设置完毕存入EEPROM、再读出来时,读出的数据和存入的数据并不一致。比如设定密度值为123.456,设定完毕存入EEPROM,然后读出来的数据是形如123.454这样的数据。
经过仔细研究最终发现,问题还是出现在对float型数据的处理上。由参考文献[2]可知,float型数据在存储时,存储的并不是精确值,而是近似值。而在设计程序时,定义密度设定值dEn为float型,并直接对其进行存取处理。针对这个问题的处理办法是,定义dEn为unsigned long型的数据,其中存放的数据是密度实际设定值放大1000倍后的数据,在程序其他地方用到密度设定值时,只需要将其除以1000得到真实值即可。这样在参数设定完毕存入EEPROM及从EEPROM中读出数据时,实际上都是以整数形式进行操作的,这样就可以保证数据的正确性。为了给操作者一种仍旧是在设定一个小数点后有3位小数的参数的感觉,在设定和显示参数过程中,始终点亮LCM 141相关数据位的小数点[4]。
结 语
本文讨论了笔者在设计流量计的过程中,利用C语言操作浮点数所遇到的问题,这种问题在其他仪表及嵌入式系统设计中也是普遍存在的。针对这些问题给出了解决方法,且已经成功地在实际项目中得到应用,具有一定的参考价值。
[1]HI-TECH Software.HI-TECH PICC-9.5 Compiler Manual[OL].2005[2010-03-18].http://www.htsoft.com.
[2]谭浩强.C程序设计[M].3版.北京:清华大学出版社,2007.[3]杜春树.关于智能仪表中浮点数累积精度[J].仪表技术,1997(2):13-14.
[4]青云科技.LCM 141数据手册[OL].[2010-03-18].http://www.qingyun-it.com.