2017-05-30 99 views
7
System.out.println(2.00-1.10) 

为什么投射会在java中产生正确的结果?

两个输出相同的结果0.8999999999999999,但

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

输出0.9

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

如果1.1在双那么为什么

System.out.println((double)(1.10))输出1.1只有转化为1.100000000000001

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

+2

阅读[this](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) –

+0

“也许默认情况下Java执行双倍计算”,因为'2.00'和'默认情况下,1.10'被认为是双打(除非你明确声明它们应该是浮动的,就像加上'f'后缀),所以'double + double = double'的结果解释了为什么要投射'(double)(2.00-1.10)不会改变任何东西。 – Pshemo

+0

@biziclop,虽然涉嫌重复与此问题有关;这个版本的问题有所不同。所谓的重复不会解释这个问题的最基本的方面:为什么向下转换解决了这个问题? –

回答

4

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

多少位必须打印为一个小数部分?必须至少有一位数字来表示小数部分,并且除此之外还必须包含尽可能多的数字,但数量必须与唯一地区分参数值与类型为double的相邻值所需的位数相同。也就是说,假设x是由该方法对有限非零参数产生的十进制表示法所表示的确切数学值d。然后d必须是最接近xdouble值;或者如果两个double值同样接近X,然后d必须是其中之一,并d的有效数必须0的至少显著位。

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

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

  • 首先,在编译过程中,字面1.10检查并四舍五入产生最接近double值。 (它说在JLS here此规则是在Double.valueOf(String)例如详细为double。)
  • 第二,当程序运行时,Double.toString产生一些十进制值,其在先前的步骤中产生的double值接近的String表示比任何其他double值。

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

您可以使用BigDecimal(double)构造发现一个double的实际值(或float,因为他们总能适合在double):

double必须用作源的BigDecimal,请注意,此构造函数提供了精确的转换;它不会给出与使用Double.toString(double)方法将double转换为String然后使用BigDecimal(String)构造函数相同的结果。要获得该结果,请使用staticvalueOf(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.9Double.toString不会产生或多或少的巧合。


作为一个附注,(double) (2.00 - 1.10)是一个冗余的演员。 2.001.10已经是double文字,所以评估表达式的结果已经是double。此外,要减去float,则需要投射两个操作数,如(float) 2.00 - (float) 1.10或使用float文字,如2.00f - 1.10f(float) (2.00 - 1.10)仅将结果转换为float

+0

那些多余的演员是为了检查他们是否有所作为。没有,我想。 –

+0

是的,没有区别。不过,你做这件事的理由非常好。相关的规范是:*“如果浮点型文字后缀为ASCII字母”F“或”f“,则浮点型文字的类型为”float“;否则其类型为”double“,并且可以选择后缀ASCII字母'D'或'd' *“。 (来自[此处](https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.2),紧接着两个灰色方框,分别描述浮点文字的语法。) – Radiodef

8

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

在你的具体的例子,你可以看到双和浮动是最接近0.9与此代码:

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

,其输出的规范double和float的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

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

+0

只是一个评论:'BigDecimal'没有接受'float'参数的构造函数。 Java只是把它重新放回一个'double' ... –

+1

@UsagiMiyamoto是的,但没关系,当你将一个float赋给double值时,你会得到相同的数字。 – assylias

+0

@Kenster错字 - 谢谢。 – assylias

1

您正在混淆变量的实际值与如何在屏幕上显示值。在您的示例中,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之一。

+0

我认为'toString'围绕'浮动'的价值,但为什么不'双'? –

+1

它将两者都舍入,但达到不同的有效数字位数。由于“浮动”不够准确,所以它更多。这个例子的结果看起来不同,这只是特定值的一个意外。浮点值几乎总是近似值。值0.8999999999999999和0.9可能看起来不同,但它们非常接近相同的值。几乎所有的实际目的,它们都是相同的价值。如果字符串表示看起来很重要,则应使用四舍五入为适当的有效数字的格式。 – pcarter

相关问题