Java中带浮点数的精度错误

Java中带浮点数的精度错误,java,floating-point,precision,Java,Floating Point,Precision,我想知道在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.o

我想知道在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("");
    }
  }
}
类浮动测试
{
公共静态void main(字符串[]args)
{
浮点数1=1.89f;
对于(int i=11;i<800;i*=2)
{
System.out.println(“循环值:+i”);
系统输出打印项次(i*编号1);
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开始,每次都加倍。我认为所有其他值(或至少其中许多值)都显示了正确的结果

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

编辑:正如人们提到的,我可以使用double,但在尝试之后,似乎1.89作为double乘以792仍然输出一个错误(输出是1496.8799999999)


我想我会尝试其他解决方案,比如BigDecimal,你可以用double代替float,问题不在于Java,而在于好的标准float()

您可以:

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

  • 使用任意精度的库

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


如果您真正关心精度,则应使用BigDecimal


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

Float的第一个是原语
Float
的包装类

双打更精确

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


或者,如果需要任意精度,请使用BigDecimal。如果精度至关重要,则应使用BigDecimal以确保所需精度保持不变。在实例化计算时,请记住使用字符串来实例化值,而不是双精度运算。

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

System.out.printf("%.2f%n", 1.89 * 792);
double d = 1.89 * 792;
d = Math.round(d * 100) / 100.0;
System.out.println(d);
印刷品

1496.88
1496.88
1.8899999999999999023003738329862244427204132080078125
1496.8800000000001091393642127513885498046875
如果要将结果舍入到某个精度,可以使用舍入

System.out.printf("%.2f%n", 1.89 * 792);
double d = 1.89 * 792;
d = Math.round(d * 100) / 100.0;
System.out.println(d);
印刷品

1496.88
1496.88
1.8899999999999999023003738329862244427204132080078125
1496.8800000000001091393642127513885498046875
但是,如果您在下面看到,这将按预期打印,因为有少量的隐含舍入

System.out.printf("%.2f%n", 1.89 * 792);
double d = 1.89 * 792;
d = Math.round(d * 100) / 100.0;
System.out.println(d);

(double)1.89
并不完全是1.89,这是一个近似值

新的BigDecimal(double)转换double的精确值,而不进行任何隐含的舍入。它在求double的精确值时很有用

System.out.println(new BigDecimal(1.89));
System.out.println(new BigDecimal(1496.88));
印刷品

1496.88
1496.88
1.8899999999999999023003738329862244427204132080078125
1496.8800000000001091393642127513885498046875

你的大部分问题已经被很好地涵盖了,尽管你可能仍然会从阅读中受益,以理解其他答案的作用

但是,没有人提出“为什么它只从11开始,每次都加倍”,所以下面是答案:

for(int i = 11; i < 800; i*=2)
    ╚═══╤════╝           ╚╤═╝
        │                 └───── "double the value every time"
        │
        └───── "start at 11"
for(int i=11;i<800;i*=2)
╚═══╤════╝           ╚╤═╝
│                 └───── “每次将值加倍”
│
└───── “11点开始”

在Basic、Visual Basic、FORTRAN、ALGOL或其他“原始”语言中,我从未遇到过简单算术精度的问题。令人无法理解的是,JAVA不能在不引入错误的情况下完成简单的算术运算。我只需要小数点右边的两位数就可以算帐了。使用浮点从1355.65减去1000,我得到355.650002!为了避免这个荒谬的错误,我实现了一个简单的解决方案。我通过将小数点两侧的值分隔为字符来处理输入,将它们转换为整数,再乘以1000,然后将两者相加为整数。可笑的是,糟糕的JAVA算法没有引入任何错误。

它不起作用,请尝试double number1=1.89;然后输出:792*number1,这里有一个精度错误。@Adam float是4字节(如果我没记错的话),double是8字节(取决于你所使用的系统),这使你的精度更高,但占用的空间更多space@Adam史密斯,是的,问题在于(好的)标准(见我的答案),精度的提高并不总是可见的。例如,如果将1/3表示为0.3,然后将精度加倍为0.33或将其三倍为0.333,则可以查看十进制(以10为基数)中发生的情况。反直觉的问题是,对于我们这些在十进制世界长大的人来说,我们认为1/3的表示不准确是可以的,而1/10的表示应该准确;在有限浮点编码中有很多数字的表示不准确。我知道Float类,当我创建这个示例时,这是一个错误。至于BigDecimal,我将研究它,因为很多人都建议它。谢谢double和float都是基于二进制的——简单地说,它们只是b1*1/2+b2*1/4+b3*1/8的和。。。bn/2^n,它们永远也不会精确地保存十分之一,因为你不能用有限个非零位数的二进制写下十分之一(0.1)——很抱歉提到了这么明显的东西,它可以打印出来,但是把它赋给另一个变量怎么样?假设我做了:双重测试=number1*792;它仍然有相同的误差。在大多数情况下,使用双精度时存在舍入误差。只要您的错误没有累积,并且在打印时使用适当的轮次,它就会正确。只需快速评论