当前位置:首页 > 技术交流 > 深入理解 JavaScript 中的数字类型

深入理解 JavaScript 中的数字类型

2021/08/30 10:14 分类: 技术交流 浏览:0

JS 中所有数字都是用 64 位双精度浮点数表示的。

 

虽然标准对 JS 数字定义得很清晰,但:

 

* 64 位是什么意思?

* 单精度、双精度又是什么意思?

* 浮点数又是啥?

 

其实 JS 数字相关的很多奇怪现象,都离不开上面这些知识,但要搞懂它们,光 JS 的知识还不够。是时候掏出《计算机组成原理》来补补课了!

 

(一)什么是 64 位双精度浮点数

 

数据以二进制形式存储

 

**各种数据(数字、文本、程序、音乐、视频等)在计算机中都是以二进制形式存储的。**

 

为什么用二进制存而不用其他进制存?—— 便宜。

 

大规模制造二进制电路是比较便宜的,但要制造出能够存储和处理 10 个十进制数 0~9 的电路那就难了,这需要电路能可靠地区分出十个不同电压等级的电路。

 

另外,使用二进制形式也有其他优势:

 

* 技术上容易实现:因为二进制只需要 0 和 1 这两个数码来表示,所以任何具有两个不同稳定状态的原件(双稳态电路)都可以用来表示一个数的某一位。

* 可靠:只使用 0 和 1,传输和存储时不易出错;

* 运算规则简单:相比十进制数,二进制的运算规则简单得多,四则运算都可归结为加法和移位,由此,运算线路可以得到简化,从而提高运算速度

 

位和字节

 

**计算机存储、处理信息的最小单位是「位」(bit,也就是比特)。**

 

bit 是二进制数 Binary Digit 这个英文单词的缩写。一个比特的值是 0 或 1,没办法再把它拆分成更小的信息单位了。

 

计算机通常不会只对 1 个二进制位操作,而是通常对一组二进制位操作:**1 字节(Byte)= 8 位(bit)**。

 

计算机能同时处理的位数越多,它的速度就会越快。第一个微处理只能处理 4 位数字,而如今现代计算机已经基本上都是 64 位的了,一些显卡甚至可以处理 128 位或 256 位宽的数据。

 

只要有了多个二进制位,那么就可以来表示不同数据了,如图:

 

* 如果只有 1 位,那么二进制值只可能是 0 或 1 这 2 个值;

* 如果有 2 位,那么二进制值可以是 00、01、10、11 这 4 个值;

* 如果有 3 位,那么二进制值可以是 000、001、010、011、100、101、110、111 这 8 个值;

* ......

* 如果有 n 位,那么二进制值就可以有 2 的 n 次幂个不同值;

 

这在专业术语中叫做「位模式」。

 

所以这下就知道计算机为什么能表示茫茫多不同的数字了。因为不同数字的二进制表示,每个位都可以不同!

 

浮点数的存储

 

接下来讨论浮点数,浮点数是相对于整数而言的。比如,123.456 和 13/14 这 2 个数就是浮点数。

 

**浮点数,也就是实数,是所有有理数和无理数的集合。**

 

什么是有理数、无理数?

 

有理数可以表示为分数,比如 7/12。

 

而无理数不能表示为一个整数除以另一个整数的形式,如 π、根号 2 等。

 

一个浮点数值分 2 部分存储:「数值」以及「小数点在数值中的位置」,因为小数点在数中的位置不是固定的,所以这也就是为什么叫浮点数的原因了。

 

一些天文问题一般会用科学计数法来表示浮点数,比如 1.2345 * 10 的 20 次幂。

 

多年以来,计算机系统使用了很多不同的方法来表示浮点数,最终形成一致的标准是 IEEE 754 浮点数标准,该标准下,提供了 3 种浮点数表示:

 

* 32 位单精度浮点数;

* 64 位双精度浮点数;

* 128 位四精度浮点数;

 

**精度(Precision)用来衡量数据被表示得有多好**,比如,π 就无法用二进制或十进制数来精确表示,无论用多少位都不行。如果用 5 位十进制数来表示 π,那么精度就是 1/(10 的 5次幂),如果用 20 位,那么精度就是 1/(10 的 20 次幂)。

 

双精度和单精度的主要区别就是用来表示数字的位数不同,单精度下一共需要 32 位二进制数,而双精度下需要 64 位。

 

那这 64 位具体在计算机中是如何存储的呢?双精度浮点数的存储如图:

 

`S` 是符号位,指明这个数是整数还是负数,若 S = 0,该数为负,若 S = 1,该数为正。

 

`E` 是指数位,表示将浮点数的尾数扩大或缩小 2 的 E 次方倍,并且它的偏置值是 1023。

 

`M` 是尾数位,62 位双精度浮点数存储时有 52 个有效尾数位,还有 1 个隐藏位。因为 IEEE 浮点数的尾数都是规格化的,其值在 1.0000...00 至 1.1111...11 之间(除非这个浮点数是 0,此时尾数为 0.0000...00)。由于尾数是规格化的,那么它的最高位总是 1,因此将尾数存入存储器时没必要保存最高位的 1,从而被隐藏。

 

十进制浮点数转二进制后存储:

 

举个实例,比如要将十进制浮点数 4.12 转为二进制并存储在计算机的 64 位中,那么:

 

第一步,先将 4.12 转为二进制数:

 

`4.12.toString(2); // "100.00011110101110000101000111101011100001010001111011"`

 

第二步,规格化。将小数点左移,直到尾数变为 1.xxx 的形式,每当小数点左移 1 位,指数就加 1,那么规格化后将得到:

 

1.0000011110101110000101000111101011100001010001111011 * 2^2

 

由此:

 

* 符号位 S 为 1,因为该数是正数;

* 指数位 E 为 2 + 1023 = 1025(这是十进制),转为二进制数就是:`(1025).toString(2); // "10000000001"`

* 尾数位 M 为 0000011110101110000101000111101011100001010001111011,这里省略了起始位 1

 

所以最终 4.12 这个十进制浮点数,在 64 位双精度浮点数表示法下,存储的各个位的情况是:

 

1100000000010000011110101110000101000111101011100001010001111011

 

需要了解的是,存储时位数越多,那么意味着数的表示范围越大,精度也就越高。

 

**所以,“JS 中所有数字都是用 64 位双精度浮点数表示的”这句话,告诉我们 JS 中的数字都是以 IEEE 754 的 64 位双精度标准来存储和处理的,这背后意味着有限的数表示范围,和有限的表示精准度。**

 

后文会重点讨论由于有限的表示范围和精准度带来的一些特殊现象。在这之前,还有一个重点话题,就是 JS 中如何表示一个数。

 

(二)JS 中数的表示

JS 表示浮点型直接量

 

除了一般的表示小数的写法(实数,比如 `1.01`,由整数部分、小数点、小数部分组成),JS 中还可以用“指数记数法”来表示浮点型直接量。

 

指数记数法,就是用实数乘以 10 的指数次幂:`[digits][.digits][(E|e)[(+|-)]digits]`

 

eg:

 

```js

3.11e23 // 3.11+23,也就是 3.11 * 10 的 23 次幂

7.85E-3 // 0.00785,也就是 7.85 * 10 的 -3 次幂

```

 

JS 表示进制数

 

**二进制**:

 

计算机技术中广泛采用的进制,是仅用 0 和 1 表示的数,基数是 2,进位规则“逢二进一”

 

一个数字对象,可以通过 `toString()`([`Number.prototype.toString()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toString))方法转成二进制表示的字符串,eg:

 

```

(6).toString(2); // "110"

(7).toString(2); // "111"

```

 

其中,`toString([radix])` 方法传入的参数是用于转换的基数(2 到 36)。

 

ES6 支持以 `0b`(或 `0B`)开头来直接表示二进制数,eg:

 

```js

0b110 // 6

0b111 // 7

```

 

**八进制**:

 

八进制数以 8 为基数,ES6 要求以 `0o`(或 `0O`)开始,后跟随由 0~7 表示的数字序列,eg:

 

```js

 

 

0o377 // 255,相当于十进制中的 255

0o377.toString(2); // "11111111"

Number(0o377); // 255,Number() 可以将其他进制字符串转为十进制数

```

 

PS:ES5 时代八进制数是以 0 开始的(eg. `0377`),但严格模式下,八进制直接量是禁止使用以 0 开头的。所以如果要用八进制,还是乖乖用 ES6 `0o` 开头的写法为佳。

 

**十进制**:

 

平时业务编码最常用的,基数是 10(意味着每列可以使用 0-9)。

 

用 JS 表示十进制数,eg. `1000`

 

**十六进制**:

 

十六进制数以 16 为基数,JS 中十六进制数以 `0x` 或 `0X` 为前缀,数值由 0~9 和 a~f 构成,eg:

 

`0xff // 255,相当于十进制的 255,15 * 16 + 15 = 255`

 

同样,也可以通过 `toString()` 方法将十六进制数转成其他进制数的字符串表示:

 

```js

0xff.toString(2); // "11111111"

0xff.toString(8); // "377"

0xff.toString(10); // "255"

```

 

设置 CSS 颜色时就会用到十六进制数。

#标签:JavaScript,计算机,二进制,CSS,十六进制数