zoukankan      html  css  js  c++  java
  • [译]JavaScript中的两个0

    原文:http://www.2ality.com/2012/03/signedzero.html


    译者注:文章开始之前,先看道题:

    Puzzle: A === B; 1/A < 1/B; A = ?

    你知道A等于什么吗?

    JavaScript中有两个0:-0和+0.本文解释了为什么会这样,以及它会产生哪些影响.

    1. 带符号的0

    数字需要被编码才能进行数字化存储.举个例子,假如我们要将一个整数编码为4位的二进制数,使用原码(sign-and-magnitude)方法,则最高位是符号位(0代表正,1代表负),剩下的三位表示大小(具体的值).因此,−2和+2会编码成为下面这样:

    二进制的1010表示十进制的−2
    二进制的0010表示十进制的+2

    这就意味着将会有两个零:1000(-0)和0000(0).

    在JavaScript中,所有的数字都是浮点数,都是根据IEEE 754标准中的浮点数算法以双精度格式被编码.这个标准中正负号的处理方式类似于原码(sign-and-magnitude)方法中整数的编码方式,所以也有正负零.

    JavaScript做了不少工作来故意隐藏存在有两个零的事实.

    2. 隐藏0的符号

    在JavaScript中,如果你写个0,则意味着就是+0.但是即使你写−0,引擎也会显示为0.无论你在任何的浏览器命令行或Node.js REPL中执行,都会这样显示:

    > -0
    0

    译者注:SpiderMonkey shell(js)以及v8 shell(d8)都会显示出-0.

    原因是标准的toString()方法会将两种零都转换成相同的"0".

    > (-0).toString()
    '0'
    > (+0).toString()
    '0'

    严格相等运算符也让我们有了"只存在一个零"的错觉.连严格相等都无法区分开,这给我们区分它们带来了很大困难(在某些少有的情况下,你的确需要区分它们).

    > +0 === -0
    true

    小于号和大于号运算符也类似,它们也认为这两个零是相等的.

    > -0 < +0
    false > +0 < -0 false

    3. 零的正负号对计算结果的影响

    0的正负号很少会影响计算结果.并且我们通常能见到的0都是+0.只有极少数操作会产生−0的结果,而且其中大多数操作都必须刻意传入-0才有可能得到-0的结果.本节会演示几个0的正负号影响计算结果的例子.对于每个例子,想想能不能利用它们来区分开−0和+0,最终的解决方案将会在第四节中给出.为了能让一个零的正负号显示出来,我们使用下面的这个函数.

    function signed(x) {
    if (x === 0) {
    // isNegativeZero()函数将会在下面给出 return isNegativeZero(x) ? "-0" : "+0";
    }
    else {
    // 否则,使用数字原生的toSting方法 return Number.prototype.toString.call(x);
    }
    }

    3.1. 两个零相加

    引用自ES5 11.6.3 "加法运算符作用于数字的情况":

    两个负零的和是−0.两个正零或者一个正零和一个负零的和是+0.

    例如:

    > signed(-0 + -0)
    '-0'
    > signed(-0 + +0)
    '+0'

    -0和-0的和仍然是-0,+0和-0的和仍然是+0,我们仍无法利用它们的和来区分正负0.

    3.2. 乘零

    用零乘以一个有穷数,结果的正负号符合一般规则:

    > signed(+0 * -5)
    '-0'
    > signed(-0 * -5)
    '+0'

    用零乘以一个无穷数的结果是NaN:

    > -Infinity * +0
    NaN

    3.3. 除以零

    你可以用零除任何非零的数字(包括无穷大).返回的结果是无穷大,正负号也符合一般规则.

    > 5 / +0
    Infinity
    > 5 / -0
    -Infinity
    > -Infinity / +0
    -Infinity

    需要注意的是-Infinity和+Infinity可以使用===来区分.

    > -Infinity === Infinity
    false

    用零除零结果是NaN:

    > 0 / 0
    NaN
    > +0 / -0
    NaN

    3.4. Math.pow()

    下面的表格列出了,当第一个参数是零时,函数Math.pow()的返回结果:

    pow(+0, y<0) → +∞
    pow(−0, odd y<0) → −∞
    pow(−0, even y<0) → +∞

    试验一下:

    > Math.pow(+0, -1)
    Infinity

    > Math.pow(-0, -1)
    -Infinity

    3.5. Math.atan2()

    下面的表格列出了,当有参数为零时,函数Math.atan2()的返回结果:

    atan2(+0, +0) → +0
    atan2(+0, −0) → +π
    atan2(−0, +0) → −0
    atan2(−0, −0) → −π

    atan2(+0, x<0) → +π
    atan2(−0, x<0) → −π

    根据上表,可以得出好几种办法来判断出一个零的正负号.例如:

    > Math.atan2(-0, -1)
    -3.141592653589793
    > Math.atan2(+0, -1)
    3.141592653589793

    atan2函数是少有的几个参数不为零却能产生-0结果的操作之一:

    atan2(y>0, +∞) → +0
    atan2(y<0, +∞) → −0

    因此:

    > signed(Math.atan2(-1, Infinity))
    '-0'

    3.6. Math.round()

    Math.round()是另外一个参数不为零却产生-0结果的操作:

    > signed(Math.round(-0.1))
    '-0'

    4. 区分这两个零

    判断一个零是正还是负的标准解法是用它除1,然后看计算的结果是-Infinity还是+Infinity:

    function isNegativeZero(x) {
    return x === 0 && (1/x < 0);
    }

    除了上面讲的几种解法.还有一个解法来自Allen Wirfs-Brock(译者注:TC39编辑,ES标准就是他写出来的.):

    function isNegativeZero(x) {
    if (x !== 0) return false;
    var obj = {};
    Object.defineProperty(obj,
    'z', { value: -0, configurable: false });
    try {
    // 如果x的值和z属性的当前值不相等的话,就会抛出异常. Object.defineProperty(obj, 'z', { value: x });
    }
    catch (e) {
    return false }; return true;
    }

    解释:通常情况下,你不能重新定义一个不可配置的对象属性,否则会抛出异常:

        TypeError: Cannot redefine property: z

    可是,如果你重新定义属性时指定的属性特性的值与该特性当前的值相等,则JavaScript会忽略掉这个重定义操作,不会抛出异常.其中在判断两个值是否相等时使用的运算不是===,而是一个称之为SameValue的内部算法,该算法可以区分开-0和+0.你可以从Wirfs-Brock的原文中了解更多细节(冻结一个对象会让该对象的所有属性变的不可配置).

    5. 总结

    通过本文我们知道了,由于JavaScript中数字正负号的编码方式,导致了JavaScript中存在有两个不同符号的零.不过,−0的负号通常会被引擎有意隐藏起来,我们也最好假装只有一个零存在.这主要是因为,正负零之间的区别几乎不会影响到我们正常的计算操作.即便是严格相等运算符===也不能区分开它们.如果你的确需要区分一个零到底是正还是负,文中也给出了几种解决方法.需要注意的是,这种存在有两个零的怪异表现并不是JavaScript的错误,这只是遵循了IEEE 754中关于浮点数的标准.

    6. 相关链接

  • 相关阅读:
    WPF DelegateCommand 出现Specified cast is not valid
    WPF DelegateCommand 出现Specified cast is not valid
    WPF DelegateCommand 出现Specified cast is not valid
    win10 sdk 是否向下兼容
    win10 sdk 是否向下兼容
    win10 sdk 是否向下兼容
    PHP extract() 函数
    PHP end() 函数
    PHP each() 函数
    PHP current() 函数
  • 原文地址:https://www.cnblogs.com/ziyunfei/p/2777099.html
Copyright © 2011-2022 走看看