为什么强制转换为浮动会在 java 中产生正确的结果?

Why does casting to float produce correct result in java?

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 的 return 值如何是 changed/rounded off/matched 与最接近的双倍参数。

0.9 不能表示为双精度数或浮点数。浮点计算的内部细节在SO上的各个帖子都有解答,比如:Is floating point math broken?.

在您的具体示例中,您可以使用以下代码看到最接近 0.9 的双精度和浮点数:

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

输出 0.9 的规范双精度和浮点表示形式:

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

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

您混淆了变量的实际值与值在屏幕上的显示方式。在您的示例中, toString 方法用于在显示值之前将值转换为字符串。这对要使用的位置使用默认值(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 值准确。

文档没有特别详细地解释它,但 Double.toString(double) 本质上在它产生的输出中执行一些舍入。 Double.toString 算法在整个 Java SE 中使用,包括例如PrintStream.println(double)System.out。文档是这样说的:

How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0.

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

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

  • 首先,在编译期间,检查文字 1.10 并四舍五入以生成最接近的 double 值。 (它在 JLS here the rules for this are e.g. detailed in Double.valueOf(String) 中表示 double。)
  • 其次,当程序运行时,Double.toString 生成一些十进制值的 String 表示,上一步生成的 double 值比任何其他值都更接近 double值。

恰好在第二步中转换为String通常会产生一个String,这与第一步中的文字相同。我假设这是设计使然。无论如何,文字例如1.10 不会产生 double 值,该值恰好等于 1.10.

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

When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.

// 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.9Double.toString 不产生

或多或少只是巧合。

附带说明,(double) (2.00 - 1.10) 是多余的转换。 2.001.10 已经是 double 字面量,因此计算表达式的结果已经是 double。此外,要减去 float,您需要像 (float) 2.00 - (float) 1.10 一样转换两个操作数,或者使用像 2.00f - 1.10f 这样的 float 文字。 (float) (2.00 - 1.10) 仅将结果转换为 float.