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

How to avoid floating point precision errors with floats or doubles in Java? [duplicate]

提问人:tunnuz 提问时间:3/10/2011 最后编辑:philipxytunnuz 更新时间:11/21/2023 访问量:55787

问:

我在 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

如何摆脱浮动精度误差的累积?

我尝试使用双打将误差减半,但结果是一样的。

java 浮动精度

评论

1赞 Daniel Pryden 12/12/2016
与这个问题密切相关的是:stackoverflow.com/questions/6699066/......
0赞 Simon East 11/23/2023
我不同意这个问题的结束,因为答案有有用的 Java 特定信息和代码示例,而所谓的“重复”则没有。 不幸的是,我的人数超过了其他人,他们希望几乎所有的浮点问题都指向主要的通用问题。请在此处查看有关 meta 的讨论

答:

37赞 Mark Byers 3/10/2011 #1

没有将 0.1 精确表示为 或 。由于此表示错误,结果与预期略有不同。floatdouble

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

  • 使用该类型时,请仅显示所需数量的数字。检查相等性时,无论哪种方式都允许较小的容差。double
  • 或者,使用允许您存储您尝试精确表示的数字的类型,例如可以精确表示 0.1。BigDecimal

示例代码: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赞 T.J. Crowder 3/10/2011 #2

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

评论

13赞 Jon Skeet 3/10/2011
这不是“精确”和“不精确”的问题,而是每种类型可以表示什么的问题。BigDecimal 不能精确地表示“三分之一”,就像 double 能够精确地表示“十分之一”一样。
1赞 T.J. Crowder 3/10/2011
@Jon:实际上,正如你评论的那样,我正在编辑,我说的是“精确”,而我的意思是“准确”(因为每个人都这样做,但我尽量避免这样做)。不过,关于“三分之一”的迷人观点。确实是很好的观点。
1赞 Jon Skeet 3/10/2011
我想说的是,“准确”也不一定是一个好词。这里有两个问题 - 一个是基本表示,另一个是固定或可变的大小(其中 BigDecimal 可以根据 MathContext 根据需要扩展,而 .NET 中的 System.Decimal 之类的东西始终是 128 位)。但要简明扼要地描述这绝对是一件复杂的事情:)根据所使用的 MathContext,“准确”可能适合也可能不适合 BigDecimal - 我相信对于“无限制”,如果结果不能准确表示,操作将引发异常。
0赞 T.J. Crowder 3/10/2011
@Jon:是的,正如您在该评论的早期版本中所说,简明扼要地说很复杂。:-)再次感谢三分之一的事情。在这种情况下,我真的从未考虑过无限十进制数列(这相当令人震惊)。
0赞 Jon Skeet 3/10/2011
我已经更新了我的评论,因为它比我记得的要复杂得多,因为 BigDecimal 的 MathContext :)
2赞 Tobias Schittkowski 3/10/2011 #3

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

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

评论

0赞 xehpuk 9/8/2016
@anivaler 链接从一开始就被破坏了。当这个答案发布时,Java 1.4 早已死去。
4赞 Michael Borgwardt 3/10/2011 #4

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

浮点指南:

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

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

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

有关详细信息,请阅读链接的网站。

3赞 aib 3/10/2011 #5

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

0赞 Jesper 3/10/2011 #6

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

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

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

10赞 Peter Lawrey 3/10/2011 #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,不是因为它更快,而是它通常更短/更清晰。

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

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

顺便说一句:除非有充分的理由不使用 double,否则不要使用 float。

0赞 Nick Pierpoint 3/10/2011 #8

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

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

5.01 + 4.02 = 9.03 (to 3 significant figures)

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

3赞 aka.nice 11/17/2012 #9

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

Shewchuck,“鲁棒自适应浮点几何谓词”,如果您想要更多有关如何使用浮点执行精确算术的示例 - 或者至少是作者的初衷的受控精度,http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf

2赞 Balu SKT 7/24/2015 #10

我遇到了同样的问题,使用 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);

希望对您有所帮助。

2赞 Solus 5/13/2016 #11
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));
    }
}
1赞 rdalmeida 4/14/2018 #12

首先让它成为双倍。永远不要使用 float,否则您将在使用实用程序时遇到问题。java.lang.Math

现在,如果您碰巧事先知道您想要的精度,并且它等于或小于 15,那么很容易告诉您的双精度表现。检查以下内容:

// 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;
}

现在,每当您进行操作时,您必须告诉双精度结果的行为:

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

注2:这就是为什么我们从不使用s 来表示价格,而是使用长s,其中最后 N(即 N <= 15,通常为 8)数字是十进制数字。那你就可以忘记我上面写的内容了:)