资讯专栏INFORMATION COLUMN

数字在JavaScript中是如何编译的

Moxmi / 1622人阅读

摘要:数字数字都是浮点数,按照标准进行存储。因此,只有偶数可以在范围内表示。但只有超过指数的上限才称为中的溢出。结论在这篇博文中,我们研究了如何将其浮点数转换为位。

JavaScript中的所有数字都是浮点数。这篇博客文章解释了这些浮点数如何在64位二进制内部表示。由于特别考虑,本文中的数字将用整数表示,以便在阅读本文后,您将了解在以下交互中会发生什么:

(译者注:浮点数并不一定等于小数,定点数也并不一定就是整数。所谓浮点数就是小数点在逻辑上是不固定的,而定点数只能表示小数点固定的数值,具用浮点数或定点数表示某哪一种数要看用户赋予了这个数的意义是什么。)

    > 9007199254740992 + 1
    9007199254740992

    > 9007199254740992 + 2
    9007199254740994
JavaScript数字

JavaScript数字都是浮点数,按照IEEE 754 standard标准进行存储。该标准有几种格式。 JavaScript使用binary64或双精度。正如前面的名称所表示的,数字以二进制格式存储在64位中。这些比特分配如下:分数占据比特0到51,指数占据比特52到62,符号占用比特63。

| sign (1 bit)
63

| exponent (11 bit)

62

52

| fraction (52 bit)

51

|

这些组件的工作原理如下:如果符号位为0,则数字为正数,否则为负数。粗略地说,分数包含数字的值,而指数表示该点的位置。在下面,我们经常使用二进制数字,这在浮点数方面有点不寻常。二进制数字将以前缀​​百分比符号(%)标记。虽然JavaScript数字以二进制格式存储,但默认输出为十进制[1]。在示例中,我们通常会使用该默认值。

分数

以下是表示非负浮点数的一种方法:有效数(或尾数)包含数字,作为自然数,指数指定点的左边(负指数)或右边(正指数)的点数应该转移。 JavaScript数字使用有理数作为有效数:1._f_其中_f_是52位小数。忽略符号,数字是有效数字乘以2_p_,其中_p_是指数(在稍后将解释的转换之后)。

比如:

| f = %101, p = 2 | Number: %1.101 × 22 = %110.1 |
| f = %101, p = −2 | Number: %1.101 × 2−2 = %0.01101 |
| f = 0, p = 0 | Number: %1.0 × 20 = %1 |

表示整数

整数的编码有多少位?有效数字有53个数字,一个在点之前,52个点。用_p_ = 52,我们有一个53位的自然数。唯一的问题是最高位始终为1.也就是说,我们没有全部位可供我们随意使用。分两步去除这个限制。首先,如果你需要一个最高位为0的53位数,然后是1,那么你设置_p_ = 51.分数的最低位成为该点之后的第一个数字,整数为0。依此类推,直到你处于编码数字1的_p_ = 0和_f_ = 0。

| | 52 | 51 | 50 | ... | 1 | 0 | (bits) |
| p=52 | 1 | f51 | f50 | ... | f1 | f0 | |
| p=51 | 0 | 1 | f51 | ... | f2 | f1 | f0=0 |
| | ... |
| p=0 | 0 | 0 | 0 | ... | 0 | 1 | f51=0, etc. |

其次,对于全部53位,我们仍然需要表示零。如何做到这一点在下一节中解释。请注意,由于符号是多带带存储的,因此整数的幅度(绝对值)为53位。

指数

指数的长度是11位,这意味着它的最低值是0,最高值是2047(211-1)。为了支持负指数,使用所谓的偏移二进制编码:1023是零,所有较低数字都是负数,所有较高数字都是正数。这意味着你从指数中减去1023将其转换为正常数字。因此,我们以前使用的变量_p_等于_e_-1023,并且有效数字乘以2_e_-1023。

偏移量二进制编码中的一些数字:

    %00000000000     0  →  −1023  (lowest number)
    %01111111111  1023  →      0
    %11111111111  2047  →   1024  (highest number)
                         
    %10000000000  1024  →      1
    %01111111110  1022  →     −1 

你倒置它的位并减1就能将一个数变为负数了。

特殊的指数

两个指数值是保留的:最低的一个(0)和最高的一个(2047)。 2047的指数用于无穷大和NaN(非数字)值[2]。 IEEE 754标准有许多NaN值,但JavaScript都将它们表示为单个值NaN。指数0用于两种能力。首先,如果分数也是0,那么整数就是0.由于符号是分开存储的,我们同时具有-0和+0(详见[3])。

其次,0的指数也用于表示非常小的数字(接近零)。然后该分数必须是非零的,如果是正数,则通过计算该数字

%0._f_ × 2−1022

这种表示是_非规范化_。先前讨论的表示被称为_标准化_。可以以规范化方式表示的最小的正数(非零)数是

%1.0 × 2−1022

最大的非正规化数字是

%0.1 × 2−1022

因此,在标准化和非标准化数字之间切换时没有漏洞。

总结:指数

| (−1)_s_ × %1._f_ × 2_e_−1023 | normalized, 0 < e < 2047 |
| (−1)_s_ × %0._f_ × 2_e_−1022 | denormalized, e = 0, f > 0 |
| (−1)_s_ × 0 | e = 0, f = 0 |
| NaN | e = 2047, f > 0 |
| (−1)_s_ × ∞ (infinity) | e = 2047, f = 0 |

用_p_ = e - 1023,指数的范围是

−1023 < p < 1024
小数部分

并非所有小数都可以用JavaScript精确表示,如下所示:

    > 0.1 + 0.2
    0.30000000000000004

小数部分0.1和0.2都不能精确地表示为二进制浮点数。但是,与实际值的偏差通常太小而不能显示。加法导致偏差变得可见。另一个例子:

    > 0.1 + 1 - 1
    0.10000000000000009

表示0.1对于表示分数110来说是个挑战。困难的部分是分母10,其分母的因子分解是2×5.指数只允许你用2的幂除整数,所以没有办法得到5英寸。比较:13不能精确地表示为小数部分。它近似于0.333333 ...

相反,将二进制小数表示为小数部分总是可能的,您只需要收集足够多的二进制数(其中每十个都有一个)。例如:

%0.001 = 18 = 12 × 2 × 2 = 5 × 5 × 5(2×5) × (2×5) × (2×5) = 12510 × 10 × 10 = 0.125
比较小数部分

因此,当您使用具有小数值的小数输入时,不应直接比较它们。相反,考虑舍入误差的上限。这样的上限称为machine epsilon。双精度的标准epsilon值是2-53。

    var epsEqu = function () { // IIFE, keeps EPSILON private
        var EPSILON = Math.pow(2, -53);
return function epsEqu(x, y) {
            return Math.abs(x - y) < EPSILON;
};
}();

上述功能可确保在正常比较不充分的情况下获得正确结果:

    > 0.1 + 0.2 === 0.3
    false
    > epsEqu(0.1+0.2, 0.3)
    true
最大整数

如果有人说“_x_是最大整数”,这意味着什么?这意味着可以表示范围为0≤_n_≤_x_的每个整数_n_,并且对于大于_x_的任何整数都不能成立。 253符合该法案。以前的所有数字都可以表示:

    > Math.pow(2, 53)
    9007199254740992
    > Math.pow(2, 53) - 1
    9007199254740991
    > Math.pow(2, 53) - 2
    9007199254740990

但是下一个整数不能被表示:

    > Math.pow(2, 53) + 1
    9007199254740992

253的一些方面是上限可能是令人惊讶的。我们将通过一系列问题来看待他们。要记住的一件事是整数范围的高端限制资源是分数;指数仍有增长空间。

为什么是53位?您有53位可用于幅度(不包括符号),但分数只包含52位。这怎么可能?正如您在上面看到的那样,指数提供了第53位:它移动了分数,因此除零之外的所有53位数都可以表示,并且它有一个特殊值来表示零(连同零的一部分)。

为什么最高的整数不是253-1?通常,_x_位表示最低的数字是0,最高的数字是2_x_-1。例如,最高的8位数字是255.在JavaScript中,最高分数确实用于数字253-1,但可以表示253,这要归功于指数的帮助 - 它仅仅是一个分数_f_ = 0并且指数_p_ = 53(转换后):

%1._f_ × 2_p_ = %1.0 × 253 = 253

为什么高于253的数字可以代表?

示例:

    > Math.pow(2, 53)
    9007199254740992
    > Math.pow(2, 53) + 1  // not OK
    9007199254740992
    > Math.pow(2, 53) + 2  // OK
    9007199254740994

    > Math.pow(2, 53) * 2  // OK
    18014398509481984

253×2的作品,因为指数可以使用。每乘以2只是将指数递增1并且不影响分数。因此,就最大分数而言,乘以2的幂不是问题。为了明白为什么可以加2到253,而不是1,我们用前面的表扩展53和54的附加位,以及_p_ = 53和_p_ = 54的行:

| | 54 | 53 | 52 | 51 | 50 | ... | 2 | 1 | 0 | (bits) |
| p=54 | 1 | f51 | f50 | f49 | f48 | ... | f0 | 0 | 0 | |
| p=53 | | 1 | f51 | f50 | f49 | ... | f1 | f0 | 0 | |
| p=52 | | | 1 | f51 | f50 | ... | f2 | f1 | f0 | |

查看行(_p_ = 53),应该很明显,JavaScript数字可以将位53设置为1.但是,因为分数_f_只有52位,所以位0必须为零。因此,只有偶数_x_可以在253≤_x_ <254范围内表示。在行(_p_ = 54)中,该间距增加到4的倍数,范围在254≤_x_ <255:

    > Math.pow(2, 54)
    18014398509481984
    > Math.pow(2, 54) + 1
    18014398509481984
    > Math.pow(2, 54) + 2
    18014398509481984
    > Math.pow(2, 54) + 3
    18014398509481988
    > Math.pow(2, 54) + 4
    18014398509481988

等等...

IEEE 754例外

IEEE 754标准描述了五个例外,其中一个不能计算精确的值:

无效:执行了无效操作。例如,计算负数的平方根。返回NaN [2]。

    > Math.sqrt(-1)
    NaN

除以零:返回正负无穷[2]。

    > 3 / 0
    Infinity
    > -5 / 0
    -Infinity

溢出:结果太大而无法表示。这意味着指数太高(_p_≥1024)。根据符号,有正面和负面溢出。返回正负无穷。

    > Math.pow(2, 2048)
    Infinity
    > -Math.pow(2, 2048)
    -Infinity

下溢:结果太接近零来表示。这意味着指数太低(_p_≤-1023)。返回非规格化的值或零。

    > Math.pow(2, -2048)
    0

不精确:操作产生了不准确的结果 - 要保留的分数有太多有效数字。返回一个舍入结果。

    > 0.1 + 0.2
    0.30000000000000004
    
    > 9007199254740992 + 1
    9007199254740992

#3和#4是关于指数,#5是关于分数。 #3和#5之间的区别非常微妙:在第五个例子中,我们超过了分数的上限(这将是整数计算中的溢出)。但只有超过指数的上限才称为IEEE 754中的溢出。

结论

在这篇博文中,我们研究了JavaScript如何将其浮点数转换为64位。它根据IEEE 754标准中的双精度进行。由于数字的显示方式,人们往往会忘记JavaScript不能精确地表示分母的因子分解包含2以外的数字的小数部分。例如,可以表示0.5(12),而0.6(35)不能表示。人们也往往忘记了三个组件符号,指数,一个数字的小数部分一起工作来表示一个整数。但是,当Math.pow(2,53)+ 2可以表示时,会遇到这种情况,但Math.pow(2,53)+ 1不能。

网页“IEEE-754 Analysis”允许您输入一个数字并查看其内部表示。

来源和相关阅读

这篇文章的来源:

“IEEE Standard 754 Floating-Point” by Steve Hollasch.

“Data Types and Scaling (Fixed-Point Blockset)” in the MATLAB documentation.

“IEEE 754-2008” on Wikipedia.

This post is part of a series on JavaScript numbers, which includes:

Displaying numbers in JavaScript

NaN and Infinity in JavaScript

JavaScript’s two zeros

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95003.html

相关文章

  • C语言进阶第一问:数据内存中是如何存储?(手把手带你深度剖析数据内卒中存储,超全解析,码住不

    摘要:在符号位中,表示正,表示负。我们知道对于整型来说,内存中存放的是该数的补码。在计算机系统中,数值一律用补码来表示和存储。表示有效数字,。规定对于位的浮点数,最高的位是 ...

    ghnor 评论0 收藏0
  • 悄悄掀起 WebAssembly 神秘面纱

    摘要:在拿到这块内存后,是拥有完全操作的权利的。后面定义了一个函数,并导出为函数。首先,使用在栈内压入一个位整型常数,然后使用在栈内压入一个位整型常数,之后调用指令,这个指 前端开发人员想必对现代浏览器都已经非常熟悉了吧?HTML5,CSS4,JavaScript ES6,这些已经在现代浏览器中慢慢普及的技术为前端开发带来了极大的便利。得益于 JIT(Just-in-time)技术,Java...

    qc1iu 评论0 收藏0
  • TypeScript Start: 什么是 TypeScript

    摘要:最近开始用来写项目,写起来还是挺顺畅的。和在类型上的区别被称作是一种动态脚本语言,其中有一个被疯狂诟病的特性缺乏静态强类型。当然,这是可以的,此时变量的类型已经发生改变字符串数字。 最近开始用 TypeScript 来写项目,写起来还是挺顺畅的。其实学习 TypeScript,看它的官方文档就够了,剩下就是 coding 了。我这里主要是我在 TypeScript 学习过程中记录的一些...

    JeOam 评论0 收藏0
  • What's New in JavaScript

    摘要:在和中都保留了数组的强引用,所以在中简单的清除变量内存并没有得到释放,因为还存在引用计数。而在中,它的键是弱引用,不计入引用计数中,所以当被清除之后,数组会因为引用计数为而被回收掉。其实我们主要注意的引用是不计引用计数的,就好理解了。 showImg(https://segmentfault.com/img/remote/1460000019147368?w=900&h=383); 前...

    cgh1999520 评论0 收藏0
  • [译文] JavaScript工作原理:V8引擎内部+5条优化代码窍门

    摘要:本文将会深入分析的引擎的内部实现。该引擎使用在谷歌浏览器内部。同其他现代引擎如或所做的一样,通过实现即时编译器在执行时将代码编译成机器代码。这可使正常执行期间只发生相当短的暂停。 原文 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code 几周前我们开始了一个系列博文旨在深入...

    dreamans 评论0 收藏0

发表评论

0条评论

Moxmi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<