为什么Java会将`long`隐式转换(无需强制转换)为`float`?

52

每次我认为自己已经理解了类型转换,就会发现另一种奇怪的行为。

long l = 123456789L;
float f = l;
System.out.println(f);  // outputs 1.23456792E8

考虑到long的比特深度大于float,我会认为需要显式转换才能使其编译通过。不出所料,我们看到结果失去了精度。

为什么这里不需要转换?

4个回答

54

《Java语言规范,第5章:转换和提升》(Java Language Specification, Chapter 5: Conversion and Promotion)讨论了这个问题:

5.1.2 拓宽原始类型转换

以下19种特定的原始类型转换称为拓宽原始类型转换:

  • byte 转成 short、int、long、float 或 double
  • short 转成 int、long、float 或 double
  • char 转成 int、long、float 或 double
  • int 转成 long、float 或 double
  • long 转成 float 或 double
  • float 转成 double

拓宽原始类型转换不会丢失有关数值总体大小的信息。

...

将 int 或 long 值转换为 float,或将 long 值转换为 double,可能会导致精度损失-也就是说,结果可能会丢失一些最低有效位的值。在这种情况下,生成的浮点值将是整数值的正确舍入版本。

换句话说,JLS 区分了数值总体大小的丢失和精度的丢失。

例如将 int 转成 byte 是(潜在的)数值总体大小的丢失,因为你不能将 500 存储在 byte 中。

long 转成 float 是潜在的精度损失但不是数值总体大小的丢失,因为浮点数的值范围大于 long 的值范围。

所以规则是:

  • 数值总体大小的丢失:需要显式转换;
  • 精度的丢失:不需要转换。

有点微妙?当然。但我希望这能解决问题。


6
我认为有理由主张,可能会丢失信息的转换需要特定的类型转换。这些是有损转换,可能会让开发人员感到惊讶。我可以看出它在某些情况下很方便,但在其他情况下不方便。虽然如此,询问这个潜在令人困惑的选择并不是不合理的。 - Jon Skeet
1
任何双精度或单精度浮点数与任何整数类型之间的转换(无论是向上还是向下)都可能导致精度损失。这将在任何情况下都需要显式转换。在我看来,这并非必要。 - cletus
我和jalf一样,似乎你完全反对强制类型转换。 - Eric Wilson
如果我们接受这样一个观点,即浮点数转换有时可能导致错误地将某些东西比作不可区分的,但不应该颠倒那些本来不应该颠倒的东西的等级,那么(float)1E39将被正确地视为与任何更大的东西不可区分(保持大小),但将其强制转换为double将产生一个值,它将错误地比1E308大。这是一个数百个数量级的错误。 - supercat
1
@cletus:精度丢失:不需要强制转换。如果是这种情况,为什么我们不能做float a = 3.6?其中3.6被解释为double。 - Vivin
显示剩余5条评论

53
同样的问题也可以问在从longdouble 的转换中-这两种转换都可能会丢失信息。

Java语言规范第5.1.2节表示:

扩展原始类型转换不会丢失有关数字值总体大小的信息。实际上,从整数类型到另一个整数类型的扩展转换根本不会丢失任何信息;该数字值完全保留。在strictfp表达式中从float到 double的扩展转换也精确地保留了数字值;但是,不是strictfp的这种转换可能会丢失有关转换值总体大小的信息。

将int或long值转换为float,或将long值转换为double,可能会导致精度损失-即结果可能会失去一些最低有效位的值。在这种情况下,使用IEEE 754舍入到最近模式(§4.2.4)将得出正确四舍五入的浮点值。

换句话说,即使您可能会丢失信息,但仍然知道该值仍将在目标类型的总体范围内。

当然可以选择要求所有隐式转换不丢失任何信息-因此int long到float 将是显式的,而long 到double 将是显式的。(int double 可以; double 具有足够的精度来准确表示所有 int 值。)

在某些情况下,这将非常有用-在某些情况下则不会。语言设计是一种妥协;你无法赢得全部胜利。我不确定我会做出什么决定...


1
是的,double类型具有52位小数位,这已经足够精确地表示32位整数了。 - starblue
补充一下,float 的最大值约为 3E38,而 int 在相同位数下的最大值为 2147483647。如果你将这个最大值传递给一个 int,然后打印它,再将它传递给一个 float 并再次打印它;两个值可能会不同,即“你可能会丢失信息”。long 大约是 9E18。 - Ced

19

虽然长整型在内部使用的比浮点数更多位,但Java语言运作在扩展路径上:

byte -> short -> int -> long -> float -> double

从左到右进行转换(即扩展转换)时,不需要强制类型转换(这就是为什么允许将long转换为float的原因)。从右到左进行转换(即缩小转换)时则需要显式强制类型转换。


这个回答实际上回答了问题。 - Humpity

5

我听说过这个。浮点数可以以指数形式存储,就像我们写的那样。'23500000000' 存储为 '2.35e10'。因此,浮点数有足够的空间来容纳长整型的值范围。以指数形式存储也是精度损失的原因。


考虑到这个问题已经超过五年了,并且有几个得到赞同的答案,加上大量的讨论,似乎你应该解释一下你是如何提供新信息和/或其他答案不足的。 - Eric Wilson
3
其他答案似乎没有谈到为什么一个4字节的浮点数可以存储一个8字节的长整型的幅度,但会牺牲精度。这个答案也没有完全回答这个问题,但是增加了讨论。它可能更适合作为一条评论。要真正理解这是如何可能的,需要参考和理解IEEE浮点标准。 - crush
@crush 真是惊人的是,六年前(明天)一个毫无实际意义的问题今天还能引起兴趣。 - Eric Wilson
2
正如Jon Skeet所说,“这些是有损转换,可能会让开发人员感到惊讶”。这个问题今天仍然引起了开发人员的兴趣,因为它会让他们感到惊讶。 - crush
@crush 我的观点是,真正的答案是“这就是Java的做法。”这就是为什么我看到这里有活动感到惊讶。 - Eric Wilson

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