每次我认为自己已经理解了类型转换,就会发现另一种奇怪的行为。
long l = 123456789L;
float f = l;
System.out.println(f); // outputs 1.23456792E8
考虑到long
的比特深度大于float
,我会认为需要显式转换才能使其编译通过。不出所料,我们看到结果失去了精度。
为什么这里不需要转换?
《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 的值范围。
所以规则是:
有点微妙?当然。但我希望这能解决问题。
long
到double
的转换中-这两种转换都可能会丢失信息。扩展原始类型转换不会丢失有关数字值总体大小的信息。实际上,从整数类型到另一个整数类型的扩展转换根本不会丢失任何信息;该数字值完全保留。在strictfp表达式中从float到 double的扩展转换也精确地保留了数字值;但是,不是strictfp的这种转换可能会丢失有关转换值总体大小的信息。
将int或long值转换为float,或将long值转换为double,可能会导致精度损失-即结果可能会失去一些最低有效位的值。在这种情况下,使用IEEE 754舍入到最近模式(§4.2.4)将得出正确四舍五入的浮点值。
换句话说,即使您可能会丢失信息,但仍然知道该值仍将在目标类型的总体范围内。
当然可以选择要求所有隐式转换不丢失任何信息-因此int
和long到
float
将是显式的,而long 到
double 将是显式的。(
int
到 double
可以; double 具有足够的精度来准确表示所有
int 值。)
在某些情况下,这将非常有用-在某些情况下则不会。语言设计是一种妥协;你无法赢得全部胜利。我不确定我会做出什么决定...
虽然长整型在内部使用的比浮点数更多位,但Java语言运作在扩展路径上:
byte -> short -> int -> long -> float -> double
从左到右进行转换(即扩展转换)时,不需要强制类型转换(这就是为什么允许将long转换为float的原因)。从右到左进行转换(即缩小转换)时则需要显式强制类型转换。
我听说过这个。浮点数可以以指数形式存储,就像我们写的那样。'23500000000' 存储为 '2.35e10'。因此,浮点数有足够的空间来容纳长整型的值范围。以指数形式存储也是精度损失的原因。
1E39
将被正确地视为与任何更大的东西不可区分(保持大小),但将其强制转换为double
将产生一个值,它将错误地比1E308大。这是一个数百个数量级的错误。 - supercat