int转float的精度丢失

1,389 阅读3分钟

int转float的精度丢失

先看一段代码

int d = 33554431;
float b = (float) d;
System.out.println(""+b);

不知道各位看官看完觉得输出会是什么,3.33554431E7?毕竟float的表示范围21272^{-127}21282^{128}是大于int的标识范围的,但是实际输出确是3.3554432E7,问题来了,为什么将int转为float会发生精度损失。

IEEE浮点数表示

计算机里是如何标识浮点数的?这里以float 32位单精度表示法来看一下。先看一下用二进制科学计数法的。

1SM2E-1^{S}*M*2^{E}

其中S代表符号位,0代表正数,1代表负数,M是尾数,E是阶码。

浮点数表示.png 在java中,float占4个字节,总共32位。第31位的数据代表S,后面的8位是阶码位 e23e24....e30组成的数值 并且减去一个偏置量Bias就等于真正的阶码值E Biase等于2k112^{k-1}-1=127,其中k为e所占的尾数位数, 单精度为8,双精度为16 后面的23位为尾数位 m0m1m2..m22组成的数值代表了尾数,而且尾数会因为前面阶码的取值不同而产生不同的值。 根据阶码的不同,将浮点数的表示分为三类。

规格化

这种情况阶码位不全为1或者全为0,以3.125f为例,用java打印出它的二进制数据

float f = 3.125f;
System.out.println(Integer.toBinaryString(Float.floatToIntBits(f)));

打印结果为1000000010010000000000000000000,float不是32位吗?怎么这里只要31位,因为3.125是个正数,符号位为0,所以给省略了,如果是-3.125f,可以看出打印结果是11000000010010000000000000000000。 这里我们仔细研究一下3.125的二进制数据01000000010010000000000000000000,符号位为0,代表的是正数,阶码位为100000000=128,阶码值E=128-Biase=128-127=1,尾数位10010000000000000000000=0.1001,这里需要注意的是规格化时使用所谓尾数位表示小数点右侧部分,小数点左侧等于1,所以真正的尾数M=1.1001,有了尾数和阶码,最终可以得到3.125=

101.100121-1^{0}*1.1001*2^{1}

非规格化

用来表示0,以及非常接近0的数,以5.877471754111438e-39 为例,打印出它的二进制数据,可以发现数据为00000000010000000000000000000000,首先尾数等于0.1,这里不需要再额外加1了,阶码E=1-Biase=-126, 所以

5.877471754111438e39=0.12126=1212126=21275.877471754111438e-39=0.1*2^{-126}=1*2^{-1}*2^{-126}=2*^{-127}

无穷大

这种情况是阶码位全为1,尾数位全为0

Nan

这种情况是阶码位全为1,尾数位不为0

扣一张计算系系统原理的图

image.png

为什么int转float会发生精度丢失

java中int占用4个字节,31位用来表示数值,一个符号位,而float使用24位表示尾数,int用31位表示精度,float只能使用24位 当int的值大于2 ^(24)    (或小于-2 ^(24)  )时, 转换成float就有可能发生精度的丢失.

关注我的公众号:滑板上的老砒霜