以下代码为startTime输出错误的值是否令人惊讶?
public class Temp {
public static void main(String args[]){
float duration = (float) 2.0;
long endTime = 1353728995;
long startTime = 0;
startTime = (long) (endTime - duration);
System.out.println(startTime);
}
}
它不会输出错误的值。它只是输出一个你意想不到的值。
重要的是这里发生了什么:
endTime - duration
这是实际上评估为:
(float) endTime - duration;
这就是数据丢失的地方。最接近1353728995的float
值为1353729024,最接近相减2的结果的float
为still13573729024。
这一切都遵循语言规范。JLS第15.8.2节(加法运算符)规定,二进制数字提升应用于操作数。
第5.6.2节(二进制数字促销)的开头是这样的:
当运算符对一对操作数应用二进制数字提升时,每个操作数都必须表示可转换为数字类型的值,则按顺序应用以下规则:
如果任何操作数是引用类型,则对其进行开箱转换(§5.1.8)。
加宽基元转换(§5.1.2)适用于根据以下规则转换一个或两个操作数:
如果其中一个操作数的类型为double,则另一个操作将转换为double。
否则,如果任一操作数的类型为
float
,则另一个操作数将转换为float
。。。。
因此,根据这些规则,endTime
的long
值被转换为float
,然后在float
算术中执行减法。
请记住,float
只提供了7个重要的准确度数字,我希望其余的都是显而易见的。
请注意,如果不将结果强制转换为long
,编译器将更清楚地了解发生了什么:
Test.java:8: error: possible loss of precision
long startTime = endTime - duration;
^
required: long
found: float
1 error
这清楚地表明,结果将是float
,这应该会对如何执行操作以及预期的准确性敲响警钟。
如果希望结果为long,请将浮点转换为long。
float duration = (float) 2.0;
long endTime = 1353728995;
long startTime = 0;
startTime = endTime - (long)duration;
System.out.println(startTime);
输出:
1353728993
如果不进行强制转换,它将被视为浮点值,结果将出乎意料。
double
和float
经常会出现精度损失。这很可能就是你在这里看到的。
注意,你实际上是在计算:
startTime = (long) ((float)endTime - duration);
因为编译器将自动将long转换为用于减法的浮点值。这是有据可查的,一些好的代码检查器实际上会警告您这一点。
你可能想要的是:
startTime = endTime - (long)duration;
但你应该这么说。
你可能也可以通过使用得到正确的
startTime = (long) (endTime - (double)duration);
但这也不安全。
这都是因为浮点表示法
当您使用float
作为至少一个参数执行任何操作时,所有参与的数字都将提升为float
。在这里,您的endTime
在减法之前被转换为1.35372902E9
作为浮点值,因为这使用了指数表示的IEEE 754 notation
这个表示不是很精确,因此您会看到不同的结果
哪里好像你做
startTime = (endTime - (long)duration);
然后它的操作使用long,你应该会得到预期的结果。