改变行为可能导致精度损失

Posted

技术标签:

【中文标题】改变行为可能导致精度损失【英文标题】:Varying behavior for possible loss of precision 【发布时间】:2011-02-11 09:55:49 【问题描述】:

在 Java 中,当你这样做时

int b = 0;
b = b + 1.0;

您可能会丢失精度错误。但是如果你这样做了,为什么会这样

int b = 0;
b += 1.0;

没有错误吗?

【问题讨论】:

也许吧,也许是因为前者将 b 转换为浮点数,然后添加,然后将其转换回整数?后者可能会将 1.0 转换为整数,并进行整数加法?不过只是猜测。 【参考方案1】:

这是因为b += 1.0; 等价于b = (int) ((b) + (1.0));。 narrowing primitive conversion (JLS 5.1.3) 隐藏在复合赋值操作中。

JLS 15.26.2 Compound Assignment Operators(JLS第三版):

E1 op= E2 形式的复合赋值表达式等价于 E1 = (T)((E1) op (E2)),其中 TE1 的类型,除了 E1 只计算一次。

例如下面的代码是正确的:

short x = 3;
x += 4.6;

并导致x 具有值7,因为它相当于:

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 PuzzlersPuzzle 9: Tweedledum 的主题。以下是本书的部分摘录(为简洁起见,略作编辑):

许多程序员认为x += i; 只是x = x + i; 的简写。这并不完全正确:如果结果的类型比变量的类型更宽,则复合赋值运算符会执行无声的缩小原语转换。

为避免令人不快的意外,不要对类型为byteshortchar 的变量使用复合赋值运算符。对int 类型的变量使用复合赋值运算符时,请确保右侧的表达式不是longfloatdouble 类型。对float 类型的变量使用复合赋值运算符时,请确保右侧的表达式不是double 类型。这些规则足以防止编译器生成危险的窄化类型转换。

对于语言设计者来说,复合赋值运算符生成隐形强制转换可能是一个错误;变量的类型比计算结果窄的复合赋值可能是非法的。

最后一段值得注意:C# 在这方面要严格得多(参见C# Language Specification 7.13.2 Compound assignment)。

【讨论】:

谢谢。当您要使用的关键字类似于“+=”时,很难搜索文档。 :P 所有op= 运算符在Java 中都称为“复合赋值”。但是,是的,我确实认为在大多数搜索引擎中查询符号而不是关键字是很困难的。 这里有 Google 员工吗?我想要一个搜索框,它还考虑到 + = 等符号 :) 是的,我希望有一种方法可以将常规谷歌与谷歌代码搜索中的正则表达式一起使用。但话又说回来,他们需要改变整个索引系统。 我刚刚被这种行为所吸引,想知道为什么 Java 设计人员添加了这种隐式转换?这样做有什么好处?主动添加没有好处的额外行为似乎太奇怪了……

以上是关于改变行为可能导致精度损失的主要内容,如果未能解决你的问题,请参考以下文章

共享库

使用全局变量有可能在导入期间改变模块行为吗?

设计模式——策略模式2

tensorflow VGG16 网络精度和损失不会改变

可能会导致.NET内存泄露的8种行为

如何处理由于分组函数而导致的 JDBC 数字类型的精度损失