如何避免Java中的浮点数或双精度浮点精度错误?

时间:2011-03-10 08:35:18

标签: java sum floating-accuracy

我有一个非常恼人的问题,在Java中使用了很多浮点数或双精度数。基本上我的想法是,如果我执行:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )
    System.out.println( value );

我得到的是:

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001

我明白浮动精度误差的累积,但是,如何摆脱这个?我尝试使用双打错误的一半,但结果仍然相同。

有什么想法吗?

12 个答案:

答案 0 :(得分:33)

floatdouble没有精确的0.1表示。由于这种表示错误,结果与您的预期略有不同。

您可以使用以下几种方法:

  • 使用double类型时,只显示所需数量的数字。检查相等性时,无论如何都允许小容差。
  • 或者使用一种类型,允许您存储您想要准确表示的数字,例如BigDecimal可以精确地表示0.1。

BigDecimal的示例代码:

BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
     value.compareTo(BigDecimal.ONE) < 0;
     value = value.add(step)) {
    System.out.println(value);
}

在线查看:ideone

答案 1 :(得分:9)

您可以使用BigDecimal等类来避免此特定问题。作为IEEE 754浮点的floatdouble并非完全准确,它们的设计速度很快。但请注意Jon的观点:BigDecimal不能准确地表示“三分之一”,超过double可以准确地表示“十分之一”。但是对于(比如说)财务计算,BigDecimal和类似的课程往往是要走的路,因为它们可以用我们人类倾向于思考它们的方式来表示数字。

答案 2 :(得分:7)

不要在迭代器中使用float / double,因为这会使舍入误差最大化。如果您只使用以下

for (int i = 0; i < 10; i++)
    System.out.println(i / 10.0);

打印

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

我知道BigDecimal是一个受欢迎的选择,但我更喜欢双倍,因为它的速度要快得多,但通常更短/更清晰。

如果计算符号数量作为代码复杂度的度量

  • 使用double =&gt; 11个符号
  • 使用BigDecimal(来自@Mark Byers示例)=&gt; 21个符号

BTW:除非确实有充分的理由不使用double,否则不要使用float。

答案 3 :(得分:3)

只是一个累积的错误(并且与Java完全无关)。 1.0f,一旦转换为实际代码,则没有值0.1 - 您已经收到舍入错误。

来自The Floating-Point Guide:

  

我该怎么做才能避免这个问题?

     

这取决于什么样的   你正在做的计算。

     
      
  • 如果你真的需要你的结果准确加起来,特别是当你使用钱时:使用特殊的十进制数据类型。
  •   
  • 如果您只是不想看到所有这些额外的小数位:只需将结果格式化为固定的   小数位数   显示它。
  •   
  • 如果您没有可用的十进制数据类型,则可以使用另一种方法   用整数,例如做钱   完全以美分计算。但   这是更多的工作,并有一些   缺点。
  •   

阅读链接到站点以获取详细信息。

答案 4 :(得分:3)

为了完整起见,我推荐这个:

Shewchuck,&#34;鲁棒自适应浮点几何谓词&#34;,如果你想要更多关于如何用浮点执行精确算术的例子 - 或者至少是控制精度,这是作者的初衷,{{ 3}}

答案 5 :(得分:2)

您应该使用十进制数据类型,而不是浮点数:

https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

答案 6 :(得分:2)

另一个解决方案是放弃==并检查两个值是否足够接近。 (我知道这不是你在身体里问的,但我正在回答问题标题。)

答案 7 :(得分:2)

我遇到过同样的问题,使用BigDecimal解决了同样的问题。以下是帮助我的片段。

double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
    total += (double)array[i];
    bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);

希望它会对你有所帮助。

答案 8 :(得分:2)

package loopinamdar;

import java.text.DecimalFormat;

public class loopinam {
    static DecimalFormat valueFormat = new DecimalFormat("0.0");

    public static void main(String[] args) {
        for (float value = 0.0f; value < 1.0f; value += 0.1f)
            System.out.println("" + valueFormat.format(value));
    }
}

答案 9 :(得分:1)

首先将其设为 double 。不要使用 float ,否则您将无法使用java.lang.Math实用程序。

现在,如果您事先知道所需的精度且等于或小于15,那么很容易告诉您的 double 行为。检查以下内容:

// the magic method:
public final static double makePrecise(double value, int precision) {
    double pow = Math.pow(10, precision);
    long powValue = Math.round(pow * value);
    return powValue / pow;
}

现在无论何时进行操作,都必须告诉 double 结果:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 1) + " => " + value );

输出:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999

如果你需要超过15的精度,那么你就不走运了:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 16) + " => " + value );

输出:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999

NOTE1:为了提高性能,您应该将Math.pow操作缓存在一个数组中。为清楚起见,这里没有做到。

注意2:这就是为什么我们永远不会将用于价格,而 long 的最后N位置(即N <= 15,通常为8)数字是十进制数字。然后你可以忘记我上面写的内容:))

答案 10 :(得分:0)

如果您想继续使用float并避免通过重复添加0.1f来累积错误,请尝试以下操作:

for (int count = 0; count < 10; count++) {
    float value = 0.1f * count;
    System.out.println(value);
}

但请注意,正如其他人已经解释的那样,float不是一种无限精确的数据类型。

答案 11 :(得分:0)

您只需要了解计算所需的精度以及所选数据类型的精确度,并相应地提供答案。

例如,如果您正在处理具有3个有效数字的数字,则使用float(提供7个有效数字的精度)是合适的。但是,如果您的起始值仅具有2位有效数字的精度,则无法将最终答案引用为7位有效数字的精确度。

5.01 + 4.02 = 9.03 (to 3 significant figures)

在您的示例中,您正在执行多次添加,并且每次添加都会对最终精度产生影响。