本文 首发于 🍀 永浩转载 请注明 来源

Java中float和double中溢值问题和浮点数的储存问题

Java中float和double中溢值问题和浮点数的储存问题

记录一下初学Java出现的问题。

以为之前是从Python起步的,最初了解到Java的数据类型有float和double这两个东西,就尝试相加这两个

img

这里返回的结果:32.45000076293945

这个返回值看的我一脸懵

要理解这个问题要先知道浮点数在计算机中是以什么形式储存的

首先要知道计算机能懂的只有0和1

每一个0和1都占一个位 bit (比特)(Binary Digits):存放一位二进制数,最小的存储单位。

所以,整数部分:

22 / 2 = 11 余0

11 / 2 = 5 余1

5 / 2 = 2 余1

2 / 2 = 1 余0

1 / 2 = 0 余1

22的二进制转换就是10110

小数部分:

0.45 * 2 = 0.9 0

0.9 * 2 = 1.8 1

0.8 * 2 = 1.6 1

0.6 * 2 = 1.2 1

0.2 * 2 = 0.4 0

0.4 * 2 = 0.8 0

0.8 * 2 = 1.6 1

0.6 * 2 = 1.2 1

0.2 * 2 = 0.4 0

……

我们可以发现0.45转化成二进制的时候是无限循环的

二进制转换完成,22.45 –> 10110.011100110……

得到这个二进制浮点数之后,计算机是怎么把他表示为没有小数点的字符呢?

就要用到小学学过的科学记数法

10110.011100110可以写为

1.0110011100110 [公式] [公式]

过程中我们发现,小数的转换有可能会产生无限循环的情况,想要做的最精确的记录22.45,计算机需要无限大的空间来记录

那么IEEE754标准就规定:

32位单精度(java中的float),使用32位(bit)来存储

64位双精度double), 使用64位储存

那采用什么样的格式呢

|S| Exp | Fraction |

+-+——–+———————–+

S:符号位(正0负1)

EXP:指数位

Fraction:有效数字

img

单精度(float)就是

|1(bit)| 8(bit) | 23(bit) |

+-+——–+———————–+

双精度(double)

|1(bit)| 11(bit) | 52(bit) |

+-+——–+———————–+

以22.45为例:

1.0110011100110 [公式] [公式] (二进制科学记数)

S = 0

EXP = 4+127 =131 –> 10000011

这里为什么是131而不是直接的4呢?

0000 0000八个位来表示指数,最大值就是1111 1111 –> 十进制就是 255

指数会有正负数两种情况,所以分两半,255 / 2 = 127.5

0~127用于负数

127~255用于正数

127相当于一个指数是0,所以表示正指数就 + 同理 -

Fraction = 0110011100110(这里只有13(bit))

= 01100111001100110011010(接着算了10(bit)补齐23(bit))

所以22.45在计算机里就是 S+EXP+Fraction = 01000001101100111001100110011010

同理**双精度(double)**有64位来记录

img

System.out.println(Integer.toBinaryString(Float.floatToIntBits(x)));

这行代码可以查看22.45的二进位表达

img

为什么没有0,因为01 跟1是一样的,所以0就不会显示了

这是-22.45

img

=======================================================

了解了IEEE二进位浮点数,知道了float和double记录的浮点精确度不一样我们再看一下问题。

img

从输出的结果看 32.45000076293945 是一个double类型,精确的表示了小数点后14位

所以float + double 是从float赋值到double

img

输出结果:22.450000762939453

可是如果float(单精度)赋值到double(双精度)出现精度丢失可以理解,但是会什么会溢值呢?

我们可以从二进制推回十进制来看一看

0 | 10000011 | 01100111001100110011010

正| 指数=4 |有效数字

1.01100111001100110011010 [公式] [公式] = 10110.0111001100110011010

后面这个0.0111001100110011010表示小数位

我们可以通过把0.0111001100110011010换成十进制来看看

小数的二进制到十进制的方法是

从小数点后依次乘以2的负一次方,2的负二次方,2的负三次方等

0.0111001100110011010

[公式]

可以直接用python一算

img

同这个结果,我们可以看出,由于float单精度只能选取 无限循环的二进制小数的23位

导致了十进制浮点数在存储时的不够精准

当我们把已经储存好的32二进制格式转换成64位时

img

输出结果:

img

可以看出Java并没有重新计算小数点后的更多位, 而是用0来补位

所以转换成double后,之前float没有精确到的位数就会显示出来。

要解决这个问题就需要使用java.math中提供的API类BigDecimal

注:(BigDecimal用String 或Integer 初始化,double初始化会有舍入精度问题)