做题学知识(2)之JS 的 Number 的标准IEEE 754

668 阅读6分钟

求职要面试,但是等到求职的时候在准备面试的内容就已经晚了。所以出这个做题学知识系列,让大家始终保持对面试的敏感度。希望大家喜欢,有好的题目也欢迎大家关注公众号进行交流。

问题

第一题

let a = 111111111111111110000
let b = 1111
a + b // 请问 a + b 的值是多少呢?

第二题

let a = 0.1
let b = 0.2
let c = 0.3

c === a + b  // 请问是 false 还是 true 呢?

答案

这俩道题主要考察了 js 数字标准的问题,js 用的是 IEEE 754 64 位双精度浮点数。所以需要注意俩个问题:

  1. 其所能表示的范围为-2^53~2^53(包括边界值)。因此当值超出这个范围的时候是不会计算的
  2. 计算小数的时候存在精度缺失的问题

针对第一题上面 a 的值明显的大于 2^53 所以答案是

111111111111111110000

针对第二题,IEEE 754 标准的 64 位双精度浮点数存在小数精度缺失。所以答案是

false

注意

1.前后端交互超过 16 位的整型

前后端交互的时候后台 id 是超过 16 为数字的整型值的时候 JSON.stringify 解析的时候会缺失。使用 axios 获取的时候默认会将 JSON 字符串进行一次 JSON.stringify。因此你得到的对象不一定是对的值哦

2.涉及金额计算的时候可以这么进行价格的比较

function epsEqu(x,y) {
  return Math.abs(x - y) < Math.pow(2, -52);
}

let a = 0.1
let b = 0.2
let c = 0.3
eqsEqu(a + b, c) // true

3.小数可以这么进行计算

let a = 0.1
let b = 0.2

(a * 10 + b * 10) / 10

核心就是将小数变为整数,计算完成后再变回小数以此来绕过小数计算的时候精度缺失的问题

扩展阅读

你想知道为什么 IEEE 754 计算小数的时候会精度缺失么?你想知道为什么超过 16 为的数字计算的时候结果会不便么?想就继续往下读吧

IEEE 754 有俩个标准一种是 64 位双精度浮点数,一种是 32 为单精度浮点数。这俩种标准主要的区别在于数的范围的大小,由于 32 位写起来容易.因此就用 32 位举例说明

32 位可以把它想象成 32 个格子。每个格子只放入 0 和 1。第1个格子代表符号正负(0为正1为负)。第 2-9 个格子代表指数,第 10-32 个格子代表尾数。

基础,十进制与二进制的来回转换

因为牵扯到十进制数与二进制数的转换,要是没有这部分基础看起来会云里雾里的。因此这里简单介绍一下计算方法。

十进制整数转二进制

举例数字 10 转二进制位 1010,规则如下,10 除以 2,直到商位0,然后从下往上取余数

式子 余数
10 / 2 5 0
5 / 2 2 1
2 / 2 1 0
1 / 2 0 1

二进制转十进制

举例二进制 1010 转十进制 10。规则如下,从右往左是 2 的 0 次幂依次递增,值相加

0 1 0 1
0 * 2^0 1 * 2^1 0 * 2^2 1 *2^3
0 2 0 8

十进制小数转二进制

举例数字 0.125 转二进制为 0.001,规则如下,小数部分乘以 2 直到结果为 0,依次取结果的整数部分

式子 乘机 小数 整数
0.125 * 2 0.25 0.25 0
0.25 * 2 0.5 0.5 0
0.5 * 2 1 0.0 1

二进制小数转十进制小数

举例二进制小数 0.001 转十进制为 0.125。规则如下, 从左往右一次是 2 的 -1 次幂依次递减,值相加

0 0 1
0 * 2^0 0 * 2^-1 0 * 2^-2
0 0 0.125

那么如何把十进制数字拆分成 32 个二进制呢?

5.5->101.1

符号位(S) 指数位(E) 尾数位(M)
0 2 1.011
0 2 + 127 去掉 1. 剩下的用 0 补全
0 1000 0001 0110 0000 0000 0000 0000 000

从上面的表格可以看出来,一个 32 位的单精度浮点数拆分成了三部分:

  • 符号位(symbol),这个最好理解。正数就存 0,负数就存 1
  • 指数位(exponent),这个就是可学技术法的幂。例如 1010 的指数是 3,为 1.01,0.001 的指数是 -3,为 1
  • 尾数位(mantissa),将二进制数科学技术之后去掉 1. 的值。不足的补 0 。多余的割舍掉

注意这里面有俩个容易混淆的地方:

  1. 指数为什么要加 127?指数给了 8 个盒子。能代表 2^8(256) 个数,因为指数可正可负。如果在用一个格子代表符号数的量级就少了一级。所以想出了一个偏移量的方式,即偏移量 127 代表 0,128 代表 1,以此往上类推。126 代表 -1,以此往下类推。因此 0 - 126 代表负数,128 - 256 代表整数

  2. 为什么去掉了 1.? 因为二进制只有俩个数。0 和 1。采用科学计数法时。取到 1 记下指数。例如 101 变成 1.01 指数是 2。0.001 变成 1 指数是 -3。因此第一位肯定是 1 存储起来没有意义。所以二进制指数移动小数点的时候别忘了前面有 1。

那么为什么超过某个数就无法计算了呢?

32 位的尾数一共 23 个字符。最多表示 23 个 1 也就是数字 2^23(8388608)。当值大于这个数的时候就会超过 23 位,因此就没法计算了。(64 位也是相同的道理)

那么为什么 0.1 + 0.2 不等于 0.3

在计算的时候并不是先将 10 进制数计算完成在存储为 2 进制。而是会先将 10 进制转换为二进制在计算,因此 0.1 转化为二进制是无限循环的应该是下面这个样子

0.1 0011 0011 0011 0011 0011 (0011 无限循环下去)

那尾数最多只能存 23 个数。所以就会给舍去,因此就出现了误差。

总结

IEEE 754 的规则远不止这么一点,像小数部分要是超过了尾数格式如何取舍。计算数字的时候会出现 NaN,正负Infinity。这些值又是咋表示的等等。有还想更详细了解的就只能自己去查询资料了,我这里只是想更清楚的讲明白上面的俩道题。

如果大家想看更多的做题学知识系列内容欢迎关注我的公众号,一些小型的文章我只在公众号里面更新哦。为了大家方便交流我还建立了微信交流群,关注公众号就能获取到哦。