1.01 + 2.01 = 3.02

2.01 * 2.01 = 4 0401   不知你注意没有,这个很寻常的等式,你如果将它放在C++中,Java中,Basic中,它

居然是不成立的。计算机在开玩笑吗?噢,对了,隐约记得这好象是浮点数的问题,似乎

很多很多年前,老师说过。还有某位姓林的先生在某本书里提过=0的判断。

嗯,如果你不遇到此问题,那你完全可以把它抛到火星上去,可惜,偶不好彩,这样的

问题,被俺遇到了。唉!

why?how?

没办法,硬着头皮,从头开始。

一:为何不成立?Why?

这得从浮点数的在计算机内的存储开始说起,我这里闲话少说。我们只谈双精度double

数(至于float,基本上是五十步和一百步的区别)。

双精度数在计算机内的表示方式是:(三部分组成)

符号(正或负)  阶码(2的N次幂)   尾数(大于等于1小于2的数)

比如: -(符号) 1.01(尾数) * 2~1(N = 1)  = - 2.02

具体到计算机的存储单元:双精度数共占8字节(64bit)

符号位(占1个bit) 阶码(11个bit) 尾数(52个bit)

解释一下:

符号位:0表示正 1表示负

阶码:是一个偏移量,1023的偏移量,它的1023相当于0,小于1023时为负,

大于1023时为正,如:10000000001表示指数为1025 - 1023 = 2,表示真值为2^2。

好了,知道了原理,我们开始分析上述等式为何为不等。

(相应数的存储值,可以简单用C语言的指针方式取出)

1.01 表示为:

0   0111111 1111    0000 00101000 11110101 11000010 10001111 01011100 00101001

2.01 表示为;

0   1000000 0000    0000 00010100 01111010 11100001 01000111 10101110 00010100

3.02 表示为:

0   1000000 0000    1000 00101000 11110101 11000010 10001111 01011100 00101001

2.01+1.01 在编程语言中的计算结果 表示为:

0   1000000 0000    1000 00101000 11110101 11000010 10001111 01011100 00101000

好了,我们可以比较一下3.02和计算结果,果然有所不同,只不过最后一个bit不同嘿。

为了验证一下,可以用手工计算一下2.01+1.01:

先把1.01的幂次变为1(与2.01的阶码相同),于是,将尾数右移一位。得到:

1000 00010100 01111010 11100001 01000111 10101110 000101001

加上2.01的尾数。

0000 00010100 01111010 11100001 01000111 10101110 00010100

得到:

1000 00101000 11110101 11000010 10001111 01011100 00101000

嗯,与计算机的计算结果相同,我们的运算思路是正确的。

因此,结论出来了,因为浮点数在计算机内的存储存在偏差,导致运算时,与实际期望的结

果不同。很多时候,你可以不理它,但是,可以肯定负责任的说,发射卫星的运算时,你

需要知道,否则,卫星一转眼就不见了。

二:不成立的的原因找到了,那怎么解决这个问题呢,How?

一个简单的解决办法是:

不要用浮点数来存储浮点,对于VC,Java,Basic,最好的办法是用Decimal来保存它。

下面是分别的实现:(以加法为例,其它四则运算处理相同)

VC中:

double doublAdd(double dbl1, double dbl2)

{

double dblResult;

DECIMAL dec1,dec2,decResult;

::VarDecFromR8(dbl1,&dec1);

::VarDecFromR8(dbl2,&dec2);

::VarDecAdd(&dec1,&dec2,&decResult);

::VarR8FromDec(&decResult,&dblResult);

return dblResult;

}

VB中:

Private Function doubleAdd(ByVal dbl1 As Double, ByVal dbl2 As Double) As Double

doubleAdd = CDec(dbl1) + CDec(dbl2)

End Function

Java中:

public static double add(double v1, double v2) {

BigDecimal b1 = new BigDecimal(Double.toString(v1));

BigDecimal b2 = new BigDecimal(Double.toString(v2));

return b1.add(b2).doubleValue();

}

解决思路就是:用其它精确的表示法来存储浮点数,就这么简单。

注意:VC示例中,VarDecFromR8是做了手脚地,如果能直接用VarDecFromStr那更好。

三:在C/C++中,似乎很不情愿看到类似上例中的代码,因为它看起来很低效,还有其它方法吗?

好象还有,对了,只是好象。

我们再来看看双精度数的表示法:

尾数一共有52个bit,也就是最小能表示的数是 2^-52,取对数可得出,约是

在小数点后16位,那也就是说小数点后15位是可以精确表示的,加上前置的默认1,一共有16位

数字是精确可靠的。我们来试验一下,看上述结论是否成立。

看看VC调试器的显示值。

2.01 的显示值: 2.0099999999999998

如果只取16位有效数字,那么将最后一位8四舍五入,我们得到正确的表示。

好了,这能说明什么呢?

四:我们先看比较简单的加,减法运算。

对于加法:dbl1 + dbl2:

假设dbl1=1.01 那么,16减去整数位1,我们可以假定,在计算机表示中:

小数点后的15位都是精确的。

假设dbl2=100.01 那么 16-3,假定小数点后13位是精确的。

凭经验我们可以知道,两个小数相加,小数点后的精度不会大于精度销大的一个。

所以,我们判定得出结果的精确度可以用较大的一个为准。

于是,将得出的结果,去掉不精确的位数,则应该可以得到准确值。

VC实现如下:

#define DELTA_RATE  16

int getRound(double dbl)

{

COleVariant var(dbl);

COleVariant varForLog(dbl);

::VarRound(&varForLog,0,&varForLog);

int nIntCount = log10(varForLog.dblVal>0?varForLog.dblVal:-varForLog.dblVal) + 1;

int nRound = DELTA_RATE - nIntCount;

return nRound;

}

double doublAdd2(double dbl1, double dbl2)

{

COleVariant var(dbl1+dbl2);

int r1 = getRound(dbl1);

int r2 = getRound(dbl2);

::VarRound(&var,max(r1,r2),&var);

return var.dblVal;

}

做过一些实验,好象是正确的。同理可以实现doubleSub2的函数。

注意:这里并不用下面五所提的取精度的方式,因为取精度的运算更低效。

五:对于乘除法呢?问题有些复杂,先找出一个需要处理的例子。

如:2.01*2.01=4.0401。

试了一下,不成立。

用方法一的Decimal方式测试,可以通过。

那么方法二呢?

再做假设吧,假设dbl1有两位小数,dbl2也有两位小数,按理论,

可得出相乘后,最大可能是2+2位小数。那么,我们按照 4位小数

进行Round处理,可能会得出正确的结果。

实际上,要取一个双精度的10进制表达的小数位,我没有找到什么好办法,

我能想到的:也就是将数字转为字串,然后查找.后的位数。这样,显然是

非常低效的,这里,我就不再写出代码了。

六:比较方法一和方法二。方法二并不高效,并且还有一些不定因素,所以,

最好采用方法一来统一处理浮点数的运算。

至于效率,实际上最佳方法是从程序的设计着手,将double从程序中去除掉。

比如在VC中,可以用Variant::Decimal来彻底替换double,这样,就不存在

中间的转换了,效率自然就提高了。有关Decimal的常用函数是:

VarDecFromStr VarDecAdd VarDecSub VarDecMul VarDecDiv ……

VarBstrFromDec

至于Java和VB,也可以方便的找到相应函数。

很想找到一种更好的方法,总觉得用Decimal来进行运算很不爽,但真的没找到?

其实呢,做了一下测试,Decimal的运算并不慢,如果可以将内部存储改为Decimal,

那就可以彻底解决问题了。

c语言浮点数如何精确计算,浮点数精确运算的分析和解决办法相关推荐

  1. c语言中延时时间计算,能精确计算C语言延时程序中延时时间的小工具(转)

    能精确计算C语言延时程序中延时时间的小工具 Emu51Form是一个软仿真计时器 具体使用方法为: 1.打开 keil\tools,ini 文件,在它的c51栏中加入 AGSI9=Emu51Form. ...

  2. 【javascript】浮点数运算问题分析及解决方法

    问题: 在用 js 进行小数四则运算时发现了一个重大问题,比如:0.7 * 0.8 = 0.5599999999999999 分析: 在 js 中只有一种数字类型 Number,而且在 js 中所有的 ...

  3. C语言的soap报文处理转义字符,webservice传递特殊字符时的解决办法

    webservice soap报文是xml格式交互的,其中针对特殊字符传递无法解析,导致数据处理失败. 解决办法如下: 1.在发送报文之前,针对报文进行base64转码,转义后避免报文中含有特殊字符. ...

  4. C语言:控制台字符无法正常输入的另一种解决办法

    在多次输入值且需要输入字符时,控制台会将之前输入的'\n'当做即将输入的字符而导致出错,例子如下: #include <stdio.h> int main(void) {int a;cha ...

  5. 安装SqlServer2008时,出现:此计算机上安装了Microsoftstudio2008早期版本解决办法

    在安装SqlServer2008版本时,提示错误:此计算机上安装了Microsoft visual studio 2008 的早期版本.请在安装SQL server 2008 前将VS2008升级到S ...

  6. Fluent计算出现“Floating point exception”时的解决办法

    "Floating point exception"代表的含义是浮点溢出,一般在运行某一算法的时候无法继续计算时出现.通常来说,浮点溢出就代表着计算不收敛.下面提供几种解决这个问题 ...

  7. Fluent计算出现浮点异常的原因及解决办法

    1.边界条件设置有误,初始条件.时间步长.松弛因子等,挨个调: 2.网格质量达不到计算要求.在Fluent中,网格质量低于0.2就不能进行计算了,当网格质量高于0.2但又比较低的话,可以用Fluent ...

  8. 【笔记】损失函数计算后返回 nan 的原因以及解决办法:logX,X为负数或0将会计算得到nan,规定X范围,可以避免nan的出现

     https://www.jianshu.com/p/ffd3e63f39ef

  9. Java中使用BigDecimal进行浮点数精确计算 超大整数 浮点数等计算,没有数位限制...

    (注:前面写了一个超大整数相加的类,参见: 超大整数相加,超过了long的范围,你要怎么做!,后来有朋友评论说BigDecimal可以完全实现我的这这个功能,刚开始的时候,我还不服气,据我所知那里有这 ...

最新文章

  1. 什么是Asp.net Core?和 .net core有什么区别?
  2. 常考数据结构与算法:将字符串转为整数
  3. 2016.11.30
  4. a标签href不跳转_[网页编程]-06 HTML5 超链接标签
  5. 疯子的算法总结(五) 矩阵乘法 (矩阵快速幂)
  6. 一个孩子能长大成人到底有多不容易? | 今日最佳
  7. 【NOI2013】向量内积【随机化】
  8. 组件文件已损坏或android内部模块,android - Android Q更新后,模块化系统组件在托管配置文件中不可用 - 堆栈内存溢出...
  9. 前端学习(1391):多人管理项目11邮箱地址查询信息
  10. python列表赋值 连续整数_列表切片赋值,小技巧(Python基础)。
  11. QT程序拷贝 转移 改变运行环境
  12. 微弱信号相关检测程序matlab,基于MATLAB微弱信号的相关检测设计与仿真.pdf
  13. shell 字符串中是否包含任一字符串
  14. linux cuda 编程指南,CUDA编程指南阅读笔记
  15. 世嘉MD游戏开发【十四】:SRAM存档和读档
  16. 如何使用kettle将EXCEL导入数据库
  17. 人体神经系统分布图图片,神经系统分布图片高清
  18. lbaas l7 policy
  19. 中国芯片争论:买关键技术还是自己重新研发?
  20. mysql qc_MySQL数据库编程中QC的使用方法

热门文章

  1. java事件溯源_领域事件与事件溯源 - 解道Jdon
  2. Sencha Touch(Extjs)
  3. Python——等额本金计算
  4. Python骚操作,让图片上的人物动起来!
  5. MongoDB day03
  6. 202007 软件市场分析
  7. 解决缺少 SHA256.h文件
  8. 智能家居之ESP8266接继电器接线方式
  9. MarkDown的第一次接触
  10. 关于阿里云ACP认证的那些事儿