为什么强制转换为浮动会在 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
方法用于在显示值之前将值转换为字符串。这对要使用的位置使用默认值(double
和 float
不同)。下面我明确设置显示多少个地方:
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.9
而 Double.toString
不产生
或多或少只是巧合。
附带说明,(double) (2.00 - 1.10)
是多余的转换。 2.00
和 1.10
已经是 double
字面量,因此计算表达式的结果已经是 double
。此外,要减去 float
,您需要像 (float) 2.00 - (float) 1.10
一样转换两个操作数,或者使用像 2.00f - 1.10f
这样的 float
文字。 (float) (2.00 - 1.10)
仅将结果转换为 float
.
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
方法用于在显示值之前将值转换为字符串。这对要使用的位置使用默认值(double
和 float
不同)。下面我明确设置显示多少个地方:
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 thedouble
value nearest to x; or if twodouble
values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be0
.
换句话说,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 inDouble.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 aBigDecimal
, note that this constructor provides an exact conversion; it does not give the same result as converting thedouble
to aString
using theDouble.toString(double)
method and then using theBigDecimal(String)
constructor. To get that result, use thestatic
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.9
而 Double.toString
不产生
附带说明,(double) (2.00 - 1.10)
是多余的转换。 2.00
和 1.10
已经是 double
字面量,因此计算表达式的结果已经是 double
。此外,要减去 float
,您需要像 (float) 2.00 - (float) 1.10
一样转换两个操作数,或者使用像 2.00f - 1.10f
这样的 float
文字。 (float) (2.00 - 1.10)
仅将结果转换为 float
.