JS - 浮点数计算(你想知道的都在这里...)

382 阅读6分钟

浮点计算

image.png

上图所示,我们可以看到0.1 + 0.2不等于0.3,而是0.30000000000000004; 你心里可能会想卧槽,啥情况,计算机难道不会这么简单的算术题吗?

下面我们就开始一一道来,希望这篇文档能给你更大的思考启发,而不是简单知道浮点计算为什么会出现误差以及如何解决。

整型计算和浮点计算

整型计算:如果2个数字a和b相加,a和b都是整数,那么在计算机中是如何相加的呢,其实很简单只需要将整数转化为二进制数,然后二进制数相加即可,只要不超过int型存储的上下限即可准确相加。其实这个计算精确的保障就是数字存储没有丢失精度,整个整型数字都完整的存储了下来,那么计算自然是准确的

浮点计算:2个带小数点的数字相加,计算结果却不那么准确,其实是因为浮点数存储在在32位(单精度浮点)或者64位(双精度浮点)中,有可能是会丢失精度的。就像1/3这种数字存进去肯定会丢失精度,因为0.33333后面是无穷的3,你没法完整的存储下来。

既然知道了浮点计算不精准的原因:存储时精度丢失;我们来看一下为什么浮点存储精度会丢失

我们看一下数字80.125怎么存储的,我们用单精度浮点存储(32位)

  1. 先分别把整数和小数部分转成2进制

image.png 整数部分直接转变成:1010000

小数部分:将小数部分0.125不断的乘以2,遇到1时停止

image.png 那么小数部分的二进制表示为:001

整个浮点数表示为1010000.001

  1. 第二步就是将1010000.001存储在32位的float中。 先看一下float的32位分别是怎么划分的

image.png

  • 符号位:存储浮点数的正负,1为正数,0位负数
  • 指数位:?
  • 尾数部分:? 这个符号位很好理解,但是指数位?尾数部分?存啥呢,一头雾水呀。而且为啥指数位是8位,尾数部分是23位,这个位数影响啥呢?莫慌,小弟一一道来。

我们看一下1010000.001前面是整数部分,后面是小数部分我们能不能把这个二进制表示的数字换个方式表示呢。什么方式?科学计数法(百度一下哈

我们简单说一下这个科学计数法: 19971400000000=1.99714×10^13 这就是科学计数法(a10^n)其中a(1≤|a|<10) 这是10进制数字的科学计数法表示,能不能把二进制数据也用科学计数法表示呢? 二进制:a2^n即可 1010000.001=1.010000001 * 2 ^ 6,这里n是6,为啥是6呢?小数点前是7位整数-1就是6(类比上面的10进制的13就是整数部分长度减-1,这个很好理解吧。。。不在阐述)

好了,我们现在用科学计算法表示的二进制数是1.010000001 * 2 ^ 6,那么我们只需要把这个科学计数法中记录的关键数据记录到32位的float类型就可以了 符号位:1(因为是正数) 指数位:我们肯定要把6这个数字信息存进去(6是这个表达式里面的指数) 尾数部分:1.010000001小数后面的就是尾数要存的,那么小数点前面的1需要存吗?不需要,因为小数点前的1是永远固定为1的(你在问为啥固定是1?二进制里面可不就是只有1和0吗,整数部分的第一个数字是0不等于没有吗,所有如果有整数部分,一定是1哈)

那么咱们在具体的塞一下数据到float里: 符号位:1 指数位:指数偏差值(2^(指数长度-1)-1)= (2^(8-1)-1) = (2^7 - 1) = 127 指数偏差值127 + 指数6 = 133 转成二进制:10000101,所以指数位的8位存储位:10000101 尾数部分:1.010000001 * 2 ^ 6 存储小数部分010000001,尾数部分23位,不足的0补齐 所以尾数部分是:01000000100000000000000

image.png

至此:80.125已经存储在了float类型上了。 那么指数区是8位,尾数区23位。有啥意义呢? 请记住下面这句话:指数区长度控制能表达的数值的上下限(毕竟存储的是指数的值,float指数区能存储的指数值是[-127, 127]); 尾部区的尾数能控制表达小数区的最大精度;如果感兴趣,可以看看这篇文章

  1. 至此,80.125这个数字没有出现精度丢失的情况,我们找一个会出现精度丢失的数字 数字:12.2 转二进制: 整数:1100 小数部分:

image.png

我们可以看到小数部分的二进制表示是无限长的,因为如上图看,1.2之后又开始了循环,那么是永远不会*2正好等于1的情况。 这种小数部分存放在尾部23位里的话,后面的精度都会丢失。

所以,存储浮点数的时候难免会出现精度丢失的情况,从而当再次拿这个丢失精度的浮点数参与计算就会出现0.1 + 0.2 !== 0.3的情况

啰里八嗦了半天,那么我们想一下怎么解决浮点运算不准确的情况

  • 我们如何判断2个浮点数是相等的,这个其实需要考虑到浮点精度 浮点数:f1,f2,如果f1-f2<0.01我们可以理解为在精度为2的情况下2个浮点数是相等的, 如果f1-f2<0.000001我们可以理解精度为6的情况下2个浮点数是相等的。

  • 所以浮点计算也需要建立在浮点精度上 上面说的0.1+0.2!==0.3如果我们把浮点精度设置为0.0001 那么0.1+0.2=0.30000000000000004 0.30000000000000004 - 0.3 < 0.0001 至此我们可以认为0.1+0.2===0.3

    在放一个加减乘除的解决方案吧。(不用太刻意记,我们只需要牢记,浮点数的相等是建立在浮点精度上的,我们需要灵活根据浮点数的特点来处理我们业务中的代码) 下面以扩大倍数运算举例子:【原理把小数变成整数,然后做运算处理,再除以放大倍数】

image.png