关于double,float的精度丢失(二)

浮点数转换与精度损失原理探究
本文深入探讨了浮点数转换过程中精度损失的原因,通过实例解析十进制到二进制的转换机制,解释了float转double与double转float时数据误差的来源。并提出了使用BigDecimal进行商业计算的建议,以避免精度损失问题。
为什么double转float不会出现数据误差,而float转double却误差如此之大?
class Text
{
        public static void main(String[] args)
        {
                float f = 0.15f;
                double d = f;
                        System.out.println("d="+d);//输出结果:0.15000000596046448        
                double d1 = 0.15;
                float  f1 = (float)d1;
                        System.out.println("f1="+f1);//输出结果:0.15
                double d2 = 0.15 + 0.15f;
                        System.out.println("d2="+d2);//输出结果:0.300000059604645
                 
                
                
        }
}
 
double转float,会丢失精度,这不难理解,因float 比double 精度低,详见“java float double精度为什么会丢失?”http://blog.youkuaiyun.com/abing37/article/details/5332798,但float、double类型计算出来的结果,为什么会不准确呢?这就有了LZ的三个问题:
1. 为什么结果会是这样呢? 
2. 如何避免这样的问题发生?
3. 如何让float转double能得到实际的数据?

问题一:为什么结果会是这样呢? 
  关键一点是, 十进制的小数,二进制表示有时不够精确。浮点数值没办法用十进制来精确表示的原因要归咎于CPU表示浮点数的方法。整数可以用二进制精确表示 ,但小数就不一定了,原因在于浮点数由两部分组成:指数和尾数。浮点数的值实际上是由一个特定的数学公式计算得到的。精度损失会在任何操作系统和编程环境中遇到,JAVA也难免。举个例子,0.9表示成二进制数:

                     0.9*2=1.8   取整数部分 1
                     0.8(1.8的小数部分)*2=1.6    取整数部分 1
                     0.6*2=1.2   取整数部分 1
                     0.2*2=0.4   取整数部分 0
                     0.4*2=0.8   取整数部分 0
                     0.8*2=1.6 取整数部分 1
                     0.6*2=1.2   取整数部分 0
                              .........      0.9二进制表示为(从上往下): 1100100100100......

           注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。

如计算12.0f-11.9f,将一个float型转化为内存存储格式的步骤为:
      (1)先将这个实数的绝对值化为二进制格式。 
     (2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。 
     (3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。 
     (4)如果实数是正的,则在第31位放入“0”,否则放入“1”。 
     (5)如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。 
     (6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。

11.9f 转为二进制:
       (1) 将11.9化为二进制后大约是(主要是0.9无法准确表示)" 1011. 1110011001100110011001100..."。
       (2) 将小数点左移三位到第一个有效位右侧: "1. 011 11100110011001100110 "。 保证有效位数24位,右侧多余的截取,误差就在这里产生了
       步骤3~6,略。
     11.9f 为:   0 1 0000010 011 11100110011001100110

计算12.0f-11.9f:
12.0f 为: 0 1 0000010 10000000000000000000000
(两浮点数进行加减,首先要看两数的 指数位 是否相同,即小数点位置是否对齐。若相同,表示小数点是对齐的,就可以进行尾数的加减运算。若两数阶码不同,必须先对阶--使两数的阶码相同)12.0f 与 11.9f 指数位完全相同,只要对有效数位进行减法即可:
     12.0f-11.9f   结果:         0 1 0000010 00000011001100110011010
     将结果还原为十进制: 0.000 11001100110011010= 0.10000038 

问题二、三:如何避免这样的问题发生,如何能得到实际的数据?
  float和double只适合做科学计算或者是工程计算,在商业计算中我们应用java.math.BigDecimal,注意一点,BigDecimal要用String来够造。我们可以下载工具类Arith,或自己调用BigDecimal,例如:
  1. public static double add(double v1,double v2){
  2. BigDecimal b1 = new BigDecimal(Double.toString(v1));
  3. BigDecimal b2 = new BigDecimal(Double.toString(v2));
  4. return b1.add(b2).doubleValue();
  5. }
在C++中,将`float`类型换为`double`类型时可能会出现精度丢失的问题,这是由于浮点数在计算机中的存储方式决定的。根据IEEE 754标准,浮点数是以进制形式存储的,其中`float`类型通常占用32位,而`double`类型占用64位。尽管`double`类型的精度高于`float`,但在某些情况下,将`float`换为`double`时仍然会出现精度丢失的现象[^4]。 ### 精度丢失的原因 1. **浮点数的进制表示**:浮点数在计算机中是以进制形式存储的,这意味着某些十进制小数无法精确表示为进制小数。例如,十进制的0.01在进制中是一个无限循环小数,因此在`float`类型中只能近似表示为0.00999999978[^1]。 2. **有效数字的限制**:`float`类型的有效数字约为6-7位,而`double`类型的有效数字约为15-16位。当`float`类型的数值换为`double`类型时,虽然`double`可以存储更多的有效数字,但由于`float`本身已经丢失了部分精度,因此换后的结果仍然不准确[^2]。 3. **格式化输出的影响**:在将浮点数换为字符串时,如果使用的格式化字符串不够精确(例如,使用`%f`而不是`%.8lf`),也会导致精度丢失。对于`float`类型,使用`%.8lf`可以避免精度丢失;而对于`double`类型,使用`%.20lf`可以更好地保留精度[^2]。 ### 解决方案 1. **使用字符串换**:一种规避精度丢失的方法是将`float`类型换为字符串,然后再换为`double`类型。这种方法可以避免直接换时的精度损失,因为字符串可以保留更多的有效数字[^1]。 ```cpp #include <sstream> #include <string> float f = 0.01f; std::stringstream ss; ss << f; std::string str = ss.str(); double d = std::stod(str); ``` 2. **使用高精度库**:对于需要更高精度的应用,可以使用第三方库如Boost.Multiprecision。该库提供了`cpp_dec_float`类,可以支持任意精度的十进制浮点数运算,从而避免精度丢失的问题[^3]。 ```cpp #include <boost/multiprecision/cpp_dec_float.hpp> using boost::multiprecision::number; using boost::multiprecision::cpp_dec_float; typedef number<cpp_dec_float<50>> cpp_dec_float_50; cpp_dec_float_50 v1("5726.867366095"); // 以字符串形式赋值,防止精度丢失 cpp_dec_float_50 v2("5837.754018494999"); const int64_t N = pow(10, 8); double newV1 = (int64_t)round(v1 * N) / double(N); double newV2 = (int64_t)round(v2 * N) / double(N); printf("%.8f\n", newV1); printf("%.8f\n", newV2); ``` 3. **格式化输出控制**:在进行浮点数到字符串的换时,合理选择格式化字符串可以减少精度丢失的可能性。对于`float`类型,建议使用`%.8lf`格式化字符串;对于`double`类型,建议使用`%.20lf`格式化字符串以保留更多有效数字。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值