浮点数的底层原理

为什么需要浮点数

我们知道实数是无穷无尽的,然而我们的计算机所能表示的数是有限的,比如32位计算机能表示2的32次方个数,虽然他已经非常多了,但是对于无限的实数来说只是沧海一粟,对于有限位数我们要想表示实数有2种方式。

一个是定点数,如果我们用4位来表示0~9,即0000->0、0001->1、0010->2…1001->9,那么32位计算机可以表示一个8长度的数字,最大值为99999999,如果还得需要表示小数那能表示的数就更小了,由此可见,定点数能表示的数字范围很小。有没有其他的办法来解决这个窘境呢?浮点数就来了。

我们在表达十进制的时候,我们知道一种表示方法叫科学计数表示法,

10000 = 1.0 * 10^4 = 10.0 * 10^3;

小数点是可以浮动的,所以我们叫他浮点数,其中上边的公式1.0、10.0我们称为有效数,4、3称为指数,计算机是如何存储浮点数的呢?

浮点数的不精确性

先来看一个简单的例子

public static void main(String[] args) {
        System.out.println(0.3+0.6);//结果0.8999999999999999
    }

简简单单的0.3+0.6,为啥结果是0.8999999999999999而不是0.9呢。要搞清楚这个结果的由来,要先知道浮点数的存储方式,有一个IEEE 754标准规定了2种浮点数的2种存储格式,一个是32位浮点数也就是单精度浮点数,第二个是64位浮点数也就是双精度浮点数。

按科学计数法二进制的表达式如下

(−1)𝑠×1.𝑓×2𝑒

其中s叫做符号位,e叫做指数位,f叫做有效位。我们拿32位浮点数来举例,看下如何存储上边的的表达式。

f有效位前边的1(就是小数点前的1)默认的都是1,称为隐藏位,无需存储;细心的你可能会发现,如果用上边的表达式是无法表示0的,的确,要表示0或者其他特殊值需要e中的0跟255,即指数位全是0或者指数位全为1.具体如下

现在,我们拿一个具体的数来看下浮点数是如何存储的,例如十进制的9.1。按照浮点数的存储方式我们需要计算出s、e、f分别是多少,要计算出这3个值,我们需要先将9.1转为二进制。

第一步先将整数位9转为二进制为1001.再将小数位转为二进制,和整数的二进制表示采用“除以 2,然后看余数”的方式相比,小数部分转换成二进制是用一个相似的反方向操作,就是乘以 2,然后看看是否超过 1。如果超过 1,我们就记下 1,并把结果减去 1,进一步循环操作。在这里,我们就会看到,0.1 其实变成了一个无限循环的二进制小数,0.000110011。这里的“0011”会无限循环下去。

所以9.1的二进制为1001.000110011… 转为浮点数为

(−1)0×1.0010001100110011….×23

由上可知,s=0 正数为0,负数为1,f=00100011001100110011 001,最后的一个“0011”循环,因位数的限制,最后一个“1”会被截断掉;e的值为3,注意,这里不是直接拿3转为二进制,需要再进行一次计算,因为e的位数是8位一共能表示255个数,既需要表示负数,也要表示正数,同时要扣除2个数(即全是0跟全是1,表示的是特殊数值),所以e能表示的最多是253个数,为了均衡表示数的范围,所以指数位在 127 之前代表负数,之后代表正数,所以e的3对应的值其实是正数位 127正向偏移3得到 130,想具体了解可以搜索【移码】查阅相关资料,总结来说就是移码无符号位比较方便。130对应的二进制为10000010,所以e=10000010。整个9.1在32位计算机中存储的形式就是这样的

如果你把这个二进制的浮点数转换为十进制会发现他不是等于9.1,他的值等于9.09999942779541015625,可以通过这个链接来直观的计算十进制的值https://www.h-schmidt.net/FloatConverter/IEEE754.html,简单说下这个怎么用,只要对应的位是1就勾上就行,然后自动计算出结果

由上我们已经知道浮点数十进制转为二进制的过程及精度丢失的原因,所以为啥0.3+0.6≠0.9就解释的通了,这是因为,浮点数没有办法精确表示 0.3、0.6 和 0.9,而只能取近似值。其实不理解浮点数的存储有时候会遇到很多完全无法理解的场景,比如下边的浮点数计算。

浮点数计算

我们先来看一个简单的例子。

public static void main(String[] args) {
        System.out.println(19000000f+1f);//结果19000000
    }

从上边可以看出+1f直接被忽略了,并没有加成功,这是为啥呢?想要知道原因我们需要知道浮点数的计算过程,简单总结就是浮点数的加法需要先指数位对齐,然后再计算,跟十进制的科学计数法类似,我们拿上面的公式来看下具体计算过程。

首先,19000000f对应的二进制为0 10010111 00100001111010101100000,1f对应 的二进制为0 01111111 00000000000000000000000,其中加粗的是对应的指数位,这个不需要自己算可以通过这个网址http://www.styb.cn/cms/ieee_754.php自动计算,很多在线进制的转换都不用手动算,可以在网上搜下,有很多在线自动计算的,毕竟自动总是比手动来的方便一些。

其次,指数位对齐,我们先计算他们之间的指数位差值,19000000f的指数位10010111(十进制151) ,1f的指数位为01111111(十进制127),通过简单的计算可以知道他们之间指数位相差151-127=24位,然后如何对齐呢 ,指数小的往大的靠,即指数位较小的数,有效位的小数点进行左移(注意左移的时候需要把隐藏位(即小数点前的1)给考虑进去),在小数点左移的过程中,最右侧的有效位就被丢弃掉了

上边表格的截图可以看出1f在进行指数对齐的时候,右侧的有效位移动完之后全变成0了,所以19000000f+1f相当于加0,所以结果还是19000000f。

如果你是一位喜欢实操的人,你可能会尝试另外一段代码

public static void main(String[] args) {
        System.out.println(19000002f+1f);
    }

你会发现他的结果是19000004f,如果我们用上边的知识来计算这段代码的结果应该是19000002f才对,怎么就变成19000004f了呢?,这里边涉及到一个知识点GRS及浮点数的舍入运算方式,这里边的知识点较细节,大概知道就行,简单来说就是小数点在左移的过程中,右边已经舍弃的值,不是直接就扔掉不管了,也是会看具体的值来影响最后结果,有点像四舍五入,比如1.823455四舍五入保留2位小数,不是直接抛弃后边的3455,而是要看小数位第三位的3是否大于5,同理浮点数在指数进行对齐的时候,右边舍弃的部分我们可以根据舍弃的具体情况组装成一个三位数的值,这个值称做GRS。当GRS满足一定规则的时候,最后一位需要进位,这个就是上边那个代码为何结果是19000004f的原因,有需要了解具体的可以网上搜索资

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值