数字在JavaScript中是如何编译的

时间: 2018-05-16阅读: 951标签: js知识作者: _小生_

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

|

这些组件的工作原理如下:如果符号位为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标准描述了五个例外,其中一个不能计算精确的值:

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

        > Math.sqrt(-1)
        NaN
    
    
  2. 除以零:返回正负无穷[2]。

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

        > Math.pow(2, 2048)
        Infinity
        > -Math.pow(2, 2048)
        -Infinity
    
    
  4. 下溢:结果太接近零来表示。这意味着指数太低(_p_≤-1023)。返回非规格化的值或零。

         > Math.pow(2, -2048)
         0
    
    
  5. 不精确:操作产生了不准确的结果 - 要保留的分数有太多有效数字。返回一个舍入结果。

        > 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”允许您输入一个数字并查看其内部表示。


来源和相关阅读

这篇文章的来源:

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

  1. Displaying numbers in JavaScript
  2. NaN and Infinity in JavaScript
  3. JavaScript’s two zeros

原文链接: 2ality.com 

 

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/772

关闭

JavaScript中变量提升和函数提升的意义理解

在ES6之前,Js中是没有块级作用域的,只存在全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分;函数提升只有函数声明中才发生。

js设备判断_判断移动端还是PC端?判断android还是ios?判断移动端浏览器类型?

js判断用户的浏览设备是移动设备还是PC?判断详细浏览器设备信息。判断微信、新浪、QQ打开。判断是android系统还是ios系统...

js不同数据类型下的toString()与toLocaleString()的输出差异

toString()与toLocaleString()方法主要针对对象Object转换为字符串,如果是基本类型调用它们的时候,先会把基本类型实例化为对应的对象类型,然后在转换为字符串。这篇文章主要讲解不同数据类型下的toString()与toLocaleString()的输出差异。

归并排序JavaScript实现_关于归并排序思路和代码实现

归并排序:其基本思想是分治策略,先进行划分,然后再进行合并,步骤是: 1.我们以数组[1, 5, 6, 2, 4, 3]举例,归并排序的第一步,将数组一分为2;2.接着将分成的数组继续一分为2,直到长度为1,我们构成如下二叉树(成树 从上往下)

7个常见的 JavaScript 测验及解答

我相信学习新事物并评估我们所知的东西对自己的进步非常有用,可以避免了我们觉得自己的知识过时的情况。在本文中,我将介绍一些常见的 JavaScript 知识。请享用!

JavaScript 优雅的实现方式包含你可能不知道的知识点【转】

Js优雅的实现方式包含你可能不知道的知识点:简短优雅地实现 sleep 函数,js获取时间戳,js数组去重,js数字格式化,js交换两个整数,将 argruments 对象(类数组)转换成数组,数字取整等

js的微任务和宏任务

宏任务:当前调用栈中执行的代码成为宏任务。微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件。

原生js实现控制函数执行次数/频率

在开发中,遇到需求如下:当函数function fn(){//...}执行的次数超过设定值后,将执行另一个函数fn2。实现方式如下

javascript不可变性是什么?

不可变性即某个变量在进行了某个操作之后,其本身没有发生变化,比如对于字符串而言,对字符串的任何操作都会改变字符串本身的值,而是在字符串的基础上复制出来一个然后再改变,这样我们就说是不可变的

一眼看穿JS变量,作用域和内存问题

这篇文章将梳理下环境,作用域链,变量对象和活动对象,以及内存管理问题。基本类型和引用类型的值,我们都知道JS中的数据类型有两大类,基本数据类型和引用数据类型,下面从三个方面来解剖他们

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!