【问题标题】:How to avoid floating point precision errors with floats or doubles in Java?java - 如何避免Java中的浮点数或双精度数的浮点精度错误?
【发布时间】:2011-07-12 13:05:54
【问题描述】:

我有一个非常烦人的问题,即 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

我知道有浮动精度误差的累积,但是,如何摆脱这个?我尝试使用 doubles 将错误减半,但结果还是一样。

有什么想法吗?

【问题讨论】:

标签: java sum floating-accuracy


【解决方案1】:

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

现在如果你碰巧事先知道你想要的 precision 并且它等于或小于 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

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

NOTE2:这就是为什么我们从不使用 doubles 来表示价格,而是 longs 在最后一个 N 处(即 N

【讨论】:

    【解决方案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));
        }
    }
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • @anivaler 链接从一开始就断开了。发布此答案时,Java 1.4 早已死去。
      【解决方案4】:

      我遇到了同样的问题,使用 BigDecimal 解决了同样的问题。下面是帮助我的sn-p。

      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);
      

      希望对你有帮助。

      【讨论】:

        【解决方案5】:

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

        Shewchuck,“Robust Adaptive Floating-Point Geometric Predicates”,如果您想了解更多关于如何使用浮点执行精确算术的示例 - 或者至少是作者最初意图的受控精度,http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf

        【讨论】:

          【解决方案6】:

          您只需要了解计算所需的精度以及您选择的数据类型能够达到的精度,并相应地给出您的答案。

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

          5.01 + 4.02 = 9.03 (to 3 significant figures)
          

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

          【讨论】:

            【解决方案7】:

            不要在迭代器中使用浮点数/双精度数,因为这会使舍入误差最大化。如果你只是使用以下

            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 不是因为它更快,而是它通常更短/更易于理解。

            如果你计算符号的数量来衡量代码的复杂度

            • 使用双精度 => 11 个符号
            • 使用 BigDecimal(来自 @Mark Byers 示例)=> 21 个符号

            顺便说一句:除非有充分的理由不使用双精度真的,否则不要使用浮点数。

            【讨论】:

              【解决方案8】:

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

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

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

              【讨论】:

                【解决方案9】:

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

                您可以使用的几种方法:

                • 当使用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

                【讨论】:

                  【解决方案10】:

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

                  【讨论】:

                    【解决方案11】:

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

                    【讨论】:

                    • 这不是“精确”和“不精确”的问题——而是每种类型可以表示什么的问题。 BigDecimal 不能准确地表示“三分之一”,就像 double 不能准确地表示“十分之一”一样。
                    • @Jon:实际上,正如你所说,我正在编辑,我说的是“精确”,我的意思是“准确”(因为每个人都这样做,但我尽量避免这样做)。不过,关于“三分之一”的迷人之处。 非常确实很好。
                    • 我想说“准确”也不一定是个好词。这里有两个问题 - 一个是基本表示,另一个是固定或可变大小(BigDecimal 可以根据 MathContext 进行扩展,而 .NET 中的 System.Decimal 总是 128 位)。但简明扼要地描述它绝对是一件复杂的事情:)“准确”可能适合也可能不适合 BigDecimal 基于所使用的 MathContext - 我相信如果使用“无限”,操作将抛出异常,如果结果无法准确表示。
                    • @Jon:是的,正如您在该评论的早期版本中所说,简明扼要地说很复杂。 :-) 再次感谢三分之一的事情。在这种情况下,我真的从未考虑过无限的十进制系列(这相当令人震惊)。
                    • 我更新了我的评论,因为 BigDecimal 的 MathContext 比我记忆中的还要复杂 :)
                    【解决方案12】:

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

                    来自The Floating-Point Guide:

                    我可以做些什么来避免这个问题?

                    这取决于是什么类型的 你正在做的计算。

                    • 如果您确实需要将结果精确相加,尤其是在使用金钱时:使用特殊的十进制数据类型。
                    • 如果您只是不想看到所有这些额外的小数位:只需将结果格式化为四舍五入为固定 时的小数位数 显示它。
                    • 如果没有可用的十进制数据类型,另一种方法是工作 带整数,例如赚钱 完全以美分计算。但 这是更多的工作,并且有一些 缺点。

                    阅读链接到的网站了解详细信息。

                    【讨论】:

                      猜你喜欢
                      • 2022-01-25
                      • 1970-01-01
                      • 2018-02-23
                      • 1970-01-01
                      • 2011-09-03
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多