【问题标题】:Java doubles adding strangely (and no, it's not for money)Java 奇怪地加倍加倍(不,这不是为了钱)
【发布时间】:2011-08-17 01:18:21
【问题描述】:

所以这是代码中唯一相关的部分

System.out.println("first term of " + firstTerm +
                   " second term of " + secondTerm + 
                   " third term of " + finalTermHolder + 
                   " should equal " + oppositeIntHolder);
double holder = firstTerm + secondTerm + finalTermHolder;
System.out.println(holder + " should equal " + oppositeIntHolder);

这是不间断的代码,它们之间没有任何内容。第一个 println 的输出是:

first term of 2.5147186257614296 second term of -9.514718625761429 third term of 7.0 should equal 0.0

第二个 println 的结果是:

8.881784197001252E-16 should equal 0.0

为什么 -9.5、2.5 和 7 加起来是 8.9 而不是 0?

【问题讨论】:

  • 加起来不是8.9,而是加起来是0.00000000000000089
  • 是8.9E-16,和8.9不一样。实际上,8.9E-16 = 0.00000000000000089,由于近似值,在浮点计算中与0相同。
  • en.wikipedia.org/wiki/Floating_point 有一篇很好的论文,如果您真的感兴趣,可以提供更多相关链接。
  • 我实际上意识到了这个错误和科学记数法,只是对 E 的含义一无所知。抱歉暂时失去情报。无论如何,修改问题。
  • 我提名这个问题及其所有疯狂的编辑作为当日的 stackoverflow 问题奖。

标签: java logic double


【解决方案1】:

它们加起来不等于 8.9。它们加起来是 8.9e-16。这就像 0.00000000000000089

即使数字显示为 -9.5 等,您仍可能会看到这一点。这是因为二进制计算机不能精确存储小数。发生小错误。是的,这正是金钱的问题。

【讨论】:

  • 问题是由于base 10和base 2之间的转换?
  • 是的。有些事情可以正常工作(实际上“.5”在二进制中是可以的,所以这里的特定值应该可以工作......)但其他人(例如“.1”)在二进制。
【解决方案2】:

8.881784197001252E-16 比你想象的更接近于零; )

Double 在 Java 中是一个浮点数。如果您正在寻找数字的精确表示,请尝试使用 BigDecimal 而不是 Double

BigDecimal num1 = new BigDecimal(3.32);
BigDecimal num2 = new BigDecimal(3.68);
System.out.println(num1.add(num2)); //will output 7.0

【讨论】:

    【解决方案3】:

    当您执行双重操作时,您需要提供适当的舍入。即使对于 BigDecimal 除法,您也需要提供适当的舍入。

    对于打印double,会进行少量舍入,因此您不会看到表示错误。但是经过几次计算(只需要一次),您的舍入误差太大,您可以看到错误。

    如果您想查看表示和舍入误差,请使用 BigDecimal,因为它会从 double 进行精确转换。本身就可能令人惊讶的事情。

    顺便说一句,2 的简单幂不会出现舍入误差。因此 -9.5 + 2.5 + 7.0 将始终为 0.0。您只会遇到其他小数(如 0.1)的舍入误差

    double[] ds = {
            0.1,
            0.2,
            -0.3,
            0.1 + 0.2 - 0.3};
    for (double d : ds) {
        System.out.println(d + " => " + new BigDecimal(d));
    }
    

    打印

    0.1 => 0.1000000000000000055511151231257827021181583404541015625
    0.2 => 0.200000000000000011102230246251565404236316680908203125
    -0.3 => -0.299999999999999988897769753748434595763683319091796875
    5.551115123125783E-17 => 5.5511151231257827021181583404541015625E-17
    

    您可以看到 0.1 和 0.2 的表示比这些值略高,-0.3 也略高。当您打印它们时,您会得到更好的 0.1 而不是表示的实际值 0.1000000000000000055511151231257827021181583404541015625

    但是,当您将这些值相加时,您会得到一个略高于 0 的值。

    要解决此问题,您需要提供适当的舍入。有了钱,这很容易,因为您知道有多少小数位是合适的,除非您有 70 万亿美元,否则您不会得到足够大的舍入误差,您无法纠正它。

    public static double roundToTwoPlaces(double d) {
        return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
    }
    

    如果将其添加到结果中,仍然会有一个小的表示错误,但它还不够大,以至于 Double.toString(d) 无法纠正它。

    double[] ds = {
            0.1,
            0.2,
            -0.3,
            0.1 + 0.2 - 0.3};
    for (double d : ds) {
        System.out.println(d + " to two places " + roundToTwoPlaces(d) + " => " + new BigDecimal(roundToTwoPlaces(d)));
    }
    

    打印

    0.1 to two places 0.1 => 0.1000000000000000055511151231257827021181583404541015625
    0.2 to two places 0.2 => 0.200000000000000011102230246251565404236316680908203125
    -0.3 to two places -0.3 => -0.299999999999999988897769753748434595763683319091796875
    5.551115123125783E-17 to two places 0.0 => 0
    

    【讨论】:

      猜你喜欢
      • 2015-11-17
      • 2019-08-31
      • 1970-01-01
      • 1970-01-01
      • 2013-11-24
      • 2010-09-23
      • 2014-11-16
      • 1970-01-01
      • 2017-05-22
      相关资源
      最近更新 更多