【问题标题】:How can I handle precision error with float in Java?如何在 Java 中使用浮点数处理精度错误?
【发布时间】:2011-07-15 22:06:58
【问题描述】:

我想知道在 Java 中修复精度错误的最佳方法是什么。正如您在以下示例中看到的那样,存在精度错误:

class FloatTest
{
  public static void main(String[] args)
  {
    Float number1 = 1.89f;
    
    for(int i = 11; i < 800; i*=2)
    {
      System.out.println("loop value: " + i);
      System.out.println(i*number1);
      System.out.println("");
    }
  }
}

显示的结果是:

循环值:11

20.789999

循环值:22

41.579998

循环值:44

83.159996

循环值:88

166.31999

循环值:176

332.63998

循环值:352

665.27997

循环值:704

1330.5599

另外,如果有人能解释为什么它只从 11 开始,并且每次都将价值翻倍。我认为所有其他值(或至少其中许多值)都显示了正确的结果。

这样的问题过去让我很头疼,我通常使用数字格式化程序或将它们放入字符串中。

编辑:正如人们所提到的,我可以使用双精度,但在尝试之后,似乎 1.89 作为双倍乘以 792 仍然会输出错误(输出为 1496.8799999999999)。

我想我会尝试其他解决方案,例如 BigDecimal

【问题讨论】:

  • double 和 float 都基于二进制系统 - 简单地说它们只是 b1* 1/2 + b2*1/4 + b3*1/8 ... bn/2^ 的总和n,它们永远不会精确地保持十分之一,因为你不能用有限数量的非零数字用二进制写下十分之一 (0.1) - 抱歉提到这些显而易见的事情
  • 你不必使用盒装类

标签: java floating-point precision


【解决方案1】:
【解决方案2】:

问题不在于 Java,而在于良好的标准浮点数 (http://en.wikipedia.org/wiki/IEEE_floating-point_standard)。

您可以:

  • 使用 Double 并且精度更高(但当然不完美,精度也有限)

  • 使用任意精度库

  • 使用数值稳定的算法并截断/舍入您不确定它们是否正确的数字(您可以计算运算的数值精度)

【讨论】:

    【解决方案3】:

    当您打印双精度运算的结果时,您需要使用适当的舍入。

    System.out.printf("%.2f%n", 1.89 * 792);
    

    打印

    1496.88
    

    如果要将结果四舍五入,可以使用四舍五入。

    double d = 1.89 * 792;
    d = Math.round(d * 100) / 100.0;
    System.out.println(d);
    

    打印

    1496.88
    

    但是,如果您在下面看到,则会按预期打印,因为存在少量隐含舍入。


    (double) 1.89 不完全是 1.89,这是一个非常接近的近似值。

    new BigDecimal(double) 转换 double 的精确值,没有任何隐含的舍入。它对于查找 double 的确切值很有用。

    System.out.println(new BigDecimal(1.89));
    System.out.println(new BigDecimal(1496.88));
    

    打印

    1.8899999999999999023003738329862244427204132080078125
    1496.8800000000001091393642127513885498046875
    

    【讨论】:

    • 它可以打印它,但是将它分配给另一个变量怎么样?假设我这样做:双重测试= number1 * 792;它仍然有同样的错误。
    • double 在大多数情况下存在舍入误差。只要您的错误没有累积,并且您在打印时使用适当的回合,它就会是正确的。
    • 快速评论 Math.round(d * 100) / 100.0;100.0 很重要,否则 java 会将其解释为 int 除法。
    • “有少量隐含舍入”是什么原因
    • @tina Java 假设您要打印最短/最接近的十进制数,其表示形式与双 c.f. 相同。 BigDecimal 可以显示实际值。
    【解决方案4】:

    您的大部分问题都已得到很好的解答,尽管您可能仍会受益于阅读 [floating-point] tag wiki 以了解为什么其他答案有效。

    但是,没有人解决“为什么它只从 11 开始并且每次都将价值翻倍”,所以这是答案:

    for(int i = 11; i < 800; i*=2)
        ╚═══╤════╝           ╚╤═╝
            │                 └───── "double the value every time"
            │
            └───── "start at 11"
    

    【讨论】:

    • 感谢标签 wiki 链接,我一定会阅读为什么它会这样工作。至于 11 和每次加倍,我知道它是如何工作的,因为我是制作示例代码的人......我的意思是,如果我制作通常的循环从 0 或 1 开始并每次递增 1 ,从 0-10 的结果显示正确的结果而没有精度损失,从 12-21 相同的东西。所以我故意做了这样的例子来更好地展示我的意思。抱歉,如果不够清楚。
    • 啊,明白了。我怀疑,在较小的值下,精度误差不足以显示在您的结果中。
    【解决方案5】:

    你可以使用双精度来代替浮点数

    【讨论】:

    • 不行,试试double number1 = 1.89;然后输出:792*number1,有精度误差。
    • @Adam 浮点数是 4 个字节(如果我没记错的话),双精度数是 8 个(取决于您使用的系统),但会占用更多空间
    • @Adam Smith,是的,问题出在(好的)标准上(见我的回答),精度的提高并不总是可见的。例如,如果您将 1/3 表示为 0.3,然后将精度加倍至 0.33 或将其三倍至 0.333,则以十进制(以 10 为底)会发生什么情况。反直觉的问题是,对于我们这些在十进制中长大的人来说,我们认为 1/3 的表示不准确是可以的,而 1/10 应该有精确的表示;有很多数字在有限浮点编码中表示不准确。
    【解决方案6】:

    如果您确实需要任意精度,请使用BigDecimal

    【讨论】:

      【解决方案7】:

      Float 的第一个是原始 float 的包装类

      双打精度更高

      但如果您只想计算到第二个数字(例如出于货币目的),请使用整数(就像您使用美分作为单位一样)并在乘法/除法时添加一些缩放逻辑

      或者如果您需要任意精度,请使用BigDecimal

      【讨论】:

      • 我知道 Float 类,创建示例时出错。至于BigDecimal,我会研究它,因为很多人都建议过它。谢谢!
      【解决方案8】:

      如果精度至关重要,您应该使用 BigDecimal 来确保保持所需的精度。实例化计算时,请记住使用字符串来实例化值而不是双精度值。

      【讨论】:

        【解决方案9】:

        在 Basic、Visual Basic、FORTRAN、ALGOL 或其他“原始”语言中,我从来没有遇到过简单的算术精度问题。 JAVA 不能在不引入错误的情况下进行简单的算术运算,这是无法理解的。我只需要小数点右边的两位数来做一些会计工作。使用浮点数从 1355.65 中减去 1000,我得到 355.650002!为了解决这个荒谬的错误,我实施了一个简单的解决方案。我通过将小数点每一侧的值分隔为字符来处理我的输入,将每个值转换为整数,将每个值乘以 1000,然后将两者加在一起作为整数。很可笑,但没有由糟糕的 JAVA 算法引入的错误。

        【讨论】:

          猜你喜欢
          • 2011-07-12
          • 2022-01-20
          相关资源
          最近更新 更多