可能丢失精度的不同行为

31

在Java中,当你执行以下操作时:

int b = 0;
b = b + 1.0;

你会得到一个可能的精度损失错误。但是如果你这样做,为什么会发生这种情况呢?

int b = 0;
b += 1.0;

没有错误吗?


也许,只是也许是因为在前者中它将b转换为浮点数,然后相加,再将其转换回整数?而在后者中,它可能将1.0转换为整数,然后进行整数相加?只是猜测而已。 - Francisco Soto
1个回答

35

这是因为b += 1.0;等同于b = (int) ((b) + (1.0));缩小原始类型转换(JLS 5.1.3)隐藏在复合赋值操作中。

JLS 15.26.2 复合赋值运算符 (JLS第三版):

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

For example, the following code is correct:

short x = 3;
x += 4.6;

and results in x having the value 7 because it is equivalent to:

short x = 3;
x = (short)(x + 4.6);
这也解释了为什么以下代码可以编译:
byte b = 1;
int x = 5;
b += x; // compiles fine!

但是这个不会:
byte b = 1;
int x = 5;
b = b + x; // DOESN'T COMPILE!

在这种情况下,您需要显式转换:
byte b = 1;
int x = 5;
b = (byte) (b + x); // now it compiles fine!

值得注意的是,复合赋值中的隐式转换是来自精彩书籍 Java Puzzlers 中的谜题9:Tweedledum的主题。以下是该书的一些摘录(为了简洁而略有编辑):
许多程序员认为x += i;只是x = x + i;的简写。这并不完全正确:如果结果的类型比变量的类型更宽,复合赋值运算符将执行一个隐式的窄化原始类型转换。
为了避免令人不快的结果,请不要在类型为byteshortchar的变量上使用复合赋值运算符。当在类型为int的变量上使用复合赋值运算符时,请确保右侧的表达式不是类型longfloatdouble。当在类型为float的变量上使用复合赋值运算符时,请确保右侧的表达式不是类型double。这些规则足以防止编译器生成危险的窄化转换。
对于语言设计者来说,复合赋值运算符生成不可见转换可能是一个错误;变量的类型比计算结果更窄的复合赋值应该是非法的。

值得注意的是最后一段:在这方面,C# 更加严格(参见 C# 语言规范 7.13.2 复合赋值)。


5
谢谢。当你想使用像"+="这样的关键词时,搜索文档变得很困难。:P - szupie
1
所有的op=运算符在Java中都被称为“复合赋值”。但是,我确实认为在大多数搜索引擎中查询符号而不是关键字很困难。 - polygenelubricants
2
这里有谷歌员工吗?我想要一个搜索框,它也考虑到像+ = <和>这样的符号 :) - extraneon
1
是的,我希望有一种方法可以在普通的谷歌搜索中使用正则表达式,就像在谷歌代码搜索中一样。但另一方面,他们需要改变他们的整个索引系统。 - szupie
我刚刚被这种行为抓住了,想知道Java设计师为什么添加了这种隐式转换?这样做有什么好处?似乎主动添加没有任何优势的额外行为太奇怪了... - mallardz
@mallardz,你还期望什么?考虑到Java是静态类型的,并且结果必须适合目标类型,这是最不令人惊讶的行为。唯一的选择是编译错误,这对用户来说并不友好。此外,它与C匹配,如果你有一个unsigned char x并且你做x++,你不希望得到一个int。 - Antimony

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接