混合浮点和长计算产生错误的答案,没有编译器警告



以下代码为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的结果的floatstill13573729024。

这一切都遵循语言规范。JLS第15.8.2节(加法运算符)规定,二进制数字提升应用于操作数。

第5.6.2节(二进制数字促销)的开头是这样的:

当运算符对一对操作数应用二进制数字提升时,每个操作数都必须表示可转换为数字类型的值,则按顺序应用以下规则:

  • 如果任何操作数是引用类型,则对其进行开箱转换(§5.1.8)。

  • 加宽基元转换(§5.1.2)适用于根据以下规则转换一个或两个操作数:

    • 如果其中一个操作数的类型为double,则另一个操作将转换为double。

    • 否则,如果任一操作数的类型为float,则另一个操作数将转换为float

    • 。。。

因此,根据这些规则,endTimelong值被转换为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

如果不进行强制转换,它将被视为浮点值,结果将出乎意料。

doublefloat经常会出现精度损失。这很可能就是你在这里看到的。

注意,你实际上是在计算:

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,你应该会得到预期的结果。

最新更新