为什么cast to float在java中产生正确的结果?

时间:2017-05-30 14:40:15

标签: java precision

System.out.println(2.00-1.10)

System.out.println((double)(2.00-1.10)) 

输出相同的结果0.8999999999999999,但

System.out.println((float)(2.00-1.10)) 

输出0.9

也许默认情况下,Java执行double计算,那么为什么向下转换会纠正结果?

如果将1.1转换为1.100000000000001,则为什么

System.out.println((double)(1.10))仅输出1.1

编辑:要了解为什么会发生这种情况,我们需要了解这两个答案。首先,规范表示在较低级别实际上是不同的。接下来,toString的返回值如何更改/舍入/匹配最接近的参数的两倍。

3 个答案:

答案 0 :(得分:8)

0.9不能表示为double或float。浮点计算的内部细节已经在SO上的各个帖子中得到了回答,例如:Is floating point math broken?

在您的具体示例中,您可以使用以下代码查看最接近0.9的double和float:

System.out.println(new BigDecimal(0.9d));
System.out.println(new BigDecimal(0.9f));

输出0.9:

的规范double和float表示
  

0.90000000000000002220446049250313080847263336181640625
  0.89999997615814208984375

现在,当您计算2.0 - 1.1时,结果为:

System.out.println(new BigDecimal(2.0-1.1));
  

0.899999999999999911182158029987476766109466552734375

你可以看到不是 0.9的规范表示,因此你会得到不同的结果。

但浮动精度不是很好,而且:

System.out.println(new BigDecimal((float) (2.0-1.1)));
  

0.89999997615814208984375

返回与0.9f的规范表示相同的数字。

答案 1 :(得分:4)

文档没有特别好地解释,但Double.toString(double)基本上在它产生的输出中执行一些舍入。在整个Java SE中使用Double.toString算法,包括例如PrintStream.println(double)的{​​{1}}。文档说明了这一点:

  

m a 的小数部分必须打印多少位数?必须至少有一个数字来表示小数部分,并且除此之外必须有多个,但只有多少,更多的数字才能唯一地将参数值与类型System.out的相邻值区分开来。也就是说,假设 x 是由此方法为有限非零参数 d 生成的十进制表示所表示的精确数学值。那么 d 必须是最接近 x double值;或者如果两个double值同样接近 x ,则 d 必须是其中之一,并且 d <的有效位的最低有效位/ em>必须是double

换句话说,它表示0的返回值不一定是参数的精确十进制表示。唯一的保证是(粗略地说)参数比任何其他toString值更接近返回值。

因此,当您执行类似doubleSystem.out.println(1.10)的打印时,这并不意味着传入的值实际上等于基数10值1.10。相反,基本上会发生以下情况:

  • 首先,在编译期间,检查文字1.10并舍入以生成最接近的1.10值。 (它在JLS here中说明了这一规则,例如double double详细说明。)
  • 其次,当程序运行时,Double.toString会生成某个十进制值的String表示形式,上一步中生成的double值比任何其他double更接近String } value。

恰好在第二步中转换为String通常会产生1.10,这与第一步中的文字相同。我认为这是设计的。无论如何,文字,例如double未生成1.10值,该值与double完全相同。

您可以使用Double.valueOf(String)构造函数发现float(或double的实际值,因为它们总是适合double):

  

如果必须将BigDecimal用作double的来源,请注意此构造函数提供了精确的转换;它与使用String方法将Double.toString(double)转换为BigDecimal(String)然后使用static构造函数的结果不同。要获得该结果,请使用valueOf(double) // 0.899999999999999911182158029987476766109466552734375 System.out.println(new BigDecimal((double) ( 2.00 - 1.10 ))); // 0.89999997615814208984375 System.out.println(new BigDecimal((float) ( 2.00 - 1.10 ))); 方法。

0.9

您可以看到两个结果实际上都不是Float.toString。在这种情况下0.9碰巧产生Double.toString并且(double) (2.00 - 1.10)没有产生2.00或多或少只是巧合。

作为旁注,1.10是一个多余的演员。 doubledouble已经是float个文字,因此评估表达式的结果已经是(float) 2.00 - (float) 1.10。另外,要减去float,您需要转换像2.00f - 1.10f这样的操作数或使用(float) (2.00 - 1.10)之类的float文字。 {{1}}仅将结果转换为{{1}}。

答案 2 :(得分:1)

您将变量的实际值与值在屏幕上的显示方式混淆。在您的示例中,toString方法用于在显示值之前将值转换为String。这使用默认值来使用多少个地方(doublefloat不同)。下面我明确设置要显示的位置数:

double d1 = 2 - 1.1;
float f1 = (float) d1;

System.out.println( String.format( "d1 = %.10f f1 = %.10f", d1, f1));
System.out.println( String.format( "d1 = %.20f f1 = %.20f", d1, f1));
System.out.println( String.format( "Using toString, d1 = %s f1 = %s", "" + d1, "" + f1));

给出输出:

d1 = 0.9000000000 f1 = 0.8999999762
d1 = 0.89999999999999990000 f1 = 0.89999997615814210000
Using toString, d1 = 0.8999999999999999 f1 = 0.9

这表明float值不如double值准确。