将Long.MAX_VALUE转换为Float

5

我一直在玩转Integer到Float、Float到Long、Long到Int的转换,但当我遇到下面的情况时感到困惑。

当我将表示Long.MAX_VALUE的字符串s(63个1)转换时,我得到了一个NumberFormatException异常,这是预期的。因为Long是64位而Integer是32位,所以有31个额外的1(这是我的猜想,如果我错了,请纠正我)。

然而,我不确定为什么在将Long转换为Float时我没有得到NumberFormatException异常。Long再次是64位,而Float与Integer一样是32位。我知道比特在Float(IEEE 754浮点“单格式”位布局)中的解释方式不同,但所有其他31个额外的比特发生了什么?我真的很迷失......

此外,如何获得9.223372E18,即1011111000000000000000000000000比特字符串?那些0来自哪里?

public static void main(String[] args){
    String s = String.valueOf(Long.MAX_VALUE); 
    print(s); //prints 9223372036854775807
    print(Long.toBinaryString(Long.MAX_VALUE)); //prints 111111111111111111111111111111111111111111111111111111111111111
    //Integer.valueOf(s) this throws NumberFormatException because Long is 64 bits and Integer is 32 so s is too large to be an Integer
    print(Float.valueOf(s)); //prints 9.223372E18 Why no NumberFormatException? and how did it result 9.223372E18?

    float f = Float.valueOf(s);
    int intBits = Float.floatToIntBits(f); 
    print(Integer.toBinaryString(intBits)); //1011111000000000000000000000000 How come? s was a String of 1s now there are 0s?
}

public static <T> void print(T arg){
    System.out.println(arg);
} 

1
我强烈建议您花时间阅读《计算机科学家应该了解的浮点运算知识》(https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)。 - Jim Garrison
3个回答

1
首先,让我们确认转换是正确的。 Long.MAX_VALUE是9223372036854775807(19位数字)。如您所见,该值大约是您打印的9.223372E18long的精度始终为1。但是,float的精度取决于数字的大小。
IEEE单精度浮点数中,即float中,存储的尾数或“分数”部分只有24位精度。因此,float表示的实际值是对Long.MAX_VALUE实际值的近似。

Float.floatToIntBits

正如您所发现的那样,Float.floatToIntBits方法产生的位与原始的long位表示不同。

根据IEEE 754浮点“单格式”位布局返回指定浮点值的表示。 位31(由掩码0x80000000选择的位)表示浮点数的符号。位30-23(由掩码0x7f800000选择的位)表示指数。位22-0(由掩码0x007fffff选择的位)表示浮点数的有效数字(有时称为尾数)。

(省略)

返回: 表示浮点数的位。

这种方法并不将转换为int,它只给出在int中存储的float的位表示。由此表示的int的值预期与float的相同。

转换

那些零是浮点数的尾数。将long转换为float的实际过程包括找到最高有效位的符号,找到值的大小以确定指数,并将其余部分的值转换为尾数。
由于Long.MAX_VALUE比例上的float的精度是有限的,因此会丢失一些精度。最终结果是float值稍微向上舍入。由于Long.MAX_VALUE比2的幂少1,所以舍入会产生2的幂,这会显示为尾数中的所有零。
你可以通过 Math.ulp(最后一位单位)来查看浮点数值的精度。
Math.ulp(f)

which yields

1.09951163E12

正如您所看到的,对于Long.MAX_VALUEfloat而言,差异非常大。(相应的double的ulp为2048.0。大,但比这里的float的ulp要小得多。)但对于接近10的19次方的值,这与float的精度预期相符,约为7位数字的精度。请保留HTML标签。

0

关于print(Float.valueOf(s)); //prints 9.223372E18 Why no NumberFormatException? and how did it result 9.223372E18?:

浮点数有不同的表示方法。所讨论的数字在 Float 范围内,根据 Java 语言规范

包括四舍五入在内的精确转换在 The JavaDoc 中有描述。

关于 `print(Integer.toBinaryString(intBits)); //1011111000000000000000000000000 How come? s was a String of 1s now there are 0s?':

Float.floatToIntBits(f) 不会返回 "与整数相同的数字"。 其语义在 The JavaDoc 中有描述。


谢谢,但我知道浮点数的表示方式与我在问题中所述不同。我想知道Java中将长整型转换为浮点数的基本机制是什么。 - OLIVER.KOO
你传递了一个代表有效浮点数的字符串。为什么会出现NumberFormatException?至于具体实现,请反编译Float.class。 - Yoav Gur
我编辑了我的答案,包括JavaDoc的描述,明确说明四舍五入是转换的一部分,并且要求进行四舍五入不被视为“非法”。 - Yoav Gur

0

float 占用四个字节(32位),long 占用八个字节(64位)。当你将 long 转换为 float 时,你会失去一半的数据,因为你无法将 64 位放入 32 位中。这就是你失去了很多位的原因。

float 使用 23 位尾数,所以大于 2^23 的整数将被截断。

这就是为什么你能够进行强制类型转换并且结果与预期相同的原因。


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