`
lijun87
  • 浏览: 268356 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

浮点数精确运算的分析和解决办法

阅读更多

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,
那就可以彻底解决问题了。

分享到:
评论

相关推荐

    java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算

    java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java 精确的浮点数运算java 精确的浮点数运算java 精确的浮点数运算 java ...

    java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类

    java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 java 精确的浮点数运算 工具类java 精确的浮点数运算 工具类 ...

    js 浮点数加法运算

    javascript浮点数加法运算精确计算方法,能够有效避免无限循环小数的产生

    4.15实验-浮点数的表示及运算

    6. 舍入:运算结果可能不是精确的二进制浮点数,需要进行舍入操作。IEEE 754提供了多种舍入策略,如四舍五入、向零舍入等。 三、浮点数的精度问题 由于浮点数的有限位存储,精度问题不可避免。近似计算可能导致...

    JAVA中浮点数的运算

    为了更好地理解浮点数运算,我们可以分析`DecimalConvertUtils.java`的代码,看它是如何处理浮点数到`BigDecimal`的转换,以及在`BigDecimal`上进行运算的实现。通常,这样的工具类会包含静态方法,便于在不同地方...

    C++实现浮点数精确加法

    C++实现浮点数精确加法 C++实现浮点数精确加法是指在计算机编程中对浮点数进行加法运算时,...C++实现浮点数精确加法需要使用字符串处理和整数运算,并且需要对浮点数的表示进行拆分和组合,以确保计算结果的正确性。

    浮点数运算[定义].pdf

    在当前提供的文档内容中,我们可以看到...从文档提供的内容来看,浮点数运算设计的各个环节都为保证数值的精确度和运算的有效性提供了必要条件。这些知识对于深入理解计算机系统中数值表示和运算有着非常重要的意义。

    MATLAB 浮点数的运算

    浮点数运算在各种科学计算、工程分析以及数据分析任务中广泛应用。MATLAB提供了丰富的函数和运算符来处理浮点数,使得浮点数运算变得高效且直观。下面将详细解释MATLAB中浮点数的运算原理、常见运算符和相关知识点。...

    解决javascript中的浮点数计算不精确问题

    4. **避免不必要的浮点数运算**:在某些情况下,可以尝试将数据转换为整数或比例来减少浮点数运算,从而降低不精确性的风险。 5. **使用BigDecimal类**:在某些高级应用中,可以考虑使用类似Java的`BigDecimal`类,...

    float_sub_add.rar_浮点数_浮点数 加减_浮点数加减_浮点数运算_浮点运算

    浮点数在计算机科学中扮演着至关重要的角色,特别是在数值计算、科学计算和图形处理等领域。...通过阅读和分析"float_sub_add.rar"中的源代码,我们可以深化对浮点数加减运算的理解,并学习如何在实践中实现这些运算。

    行业分类-设备装置-一种误差平坦的浮点数对数运算装置.zip

    1. **误差分析**:深入分析了现有浮点数对数运算的误差来源,包括数据转换、舍入和算法本身的误差。 2. **算法优化**:可能提出了新的对数运算算法,这种算法能够更均匀地分布误差,减少在特定区间内的误差积累。 ...

    浮点数运算,三菱浮点数运算,C,C++源码.zip

    浮点数运算在计算机科学中扮演着至关重要的角色,特别是在数值计算、图形处理以及各种科学...C和C++作为底层编程语言,为开发者提供了直接操控浮点数运算的强大工具,而三菱的浮点数运算技术则为工业自动化提供了便利。

    C语言浮点数运算

    ### C语言浮点数运算详解 #### 一、浮点数的有效位数为何是6~7位? 在C语言中,`float`类型的变量通常用来表示实数,其有效位数大约为6~7位。这里提到的有效位数是指能够准确表示的十进制数字的数量。之所以说是6~...

    verilog语言实现浮点数运算,正确程序代码+仿真

    浮点数运算在计算机科学中至关重要,因为它们允许更精确的数值计算,尤其是在科学计算、图形处理和工程应用中。 浮点数运算通常包括加法、减法、乘法和除法等操作,这些操作在硬件级别比整数运算更为复杂。Verilog...

    Javascript 浮点数精确计算

    总的来说,解决JavaScript中浮点数精确计算的问题需要理解浮点数的存储原理,选择合适的策略,以及可能借助外部库或自定义算法。通过`FloatCumulateV1.html`和`js`这两个文件,我们可以学习到一个具体的实现,这对于...

    简单谈谈php浮点数精确运算

    如果用php的+-*/计算浮点数的时候,可能会遇到一些计算结果错误的问题,所以基本上大部分语言都提供了精准计算的类库或函数库,比如php有BC高精确度函数库,下面我们介绍一下一些常用的BC高精确度函数使用。

    高精度浮点数幂指运算

    在计算机科学中,高精度浮点数运算是一种处理超越普通浮点类型(如double和long double)精度需求的计算方式。这些类型的浮点数虽然在大多数应用中已经足够精确,但在金融、科学计算或者数学算法等领域,更高精度的...

    python中实现精确的浮点数运算详解

    ### Python中实现精确的浮点数运算详解 #### 引言 在计算机科学领域,浮点数运算一直是程序设计中的一个重要且复杂的话题。由于计算机内存的限制与实数集不可数性的矛盾,浮点数在计算机中的表示并不总是精确的。...

Global site tag (gtag.js) - Google Analytics