JavaScript中的两个“0”(翻译)

 

本文翻译自JavaScript’s two zeros

JavaScript has two zeros: −0 and +0. This post explains why that is and where it matters in practice. 
JavaScript 中有两个“0”: -0 和 +0 。这篇文章解释了为什么,并且指出实际生产中会造成什么影响

1.The signed zero 
1.“-0”

Numbers always need to be encoded to be stored digitally. Why do some encodings have two zeros? As an example, let’s look at encoding integers as 4-digit binary numbers, via the sign-and-magnitude method. There, one uses one bit for the sign (0 if positive, 1 if negative) and the remaining bits for the magnitude (absolute value). Therefore, −2 and +2 are encoded as follows. 
Binary 1010 is decimal −2 
Binary 0010 is decimal +2 
Naturally, that means that there will be two zeros: 1000 (−0) and 0000 (+0). 
为了储存数字,需要将其编码为二级制,但为什么会编码出两个“0”呢,举个例子,将整数编码为4位二进制,由于整数有正有负,通过符号数值表示法,用第一位来表示符号(0 表示正数,1 表示负数),剩下的位表示数值(数的绝对值)。 
所以,-2 和 +2 被编码为下面的形式: 
二进制 1010 代表 -2 
二进制 0010 代表 +2 
很自然的,对于“2”也将出现两个:1000(-0) 和 0000(+0)

In JavaScript, all numbers are floating point numbers, encoded in double precision according to the IEEE 754 standard for floating point arithmetic. That standard handles the sign in a manner similar to sign-and-magnitude encoding for integers and therefore also has a signed zero. Whenever you represent a number digitally, it can become so small that it is indistinguishable from 0, because the encoding is not precise enough to represent the difference. Then a signed zero allows you to record “from which direction” you approached zero, what sign the number had before it was considered zero. Wikipedia nicely sums up the pros and cons of signed zeros: 
在JavaScript中,所有的数字都被存储为浮点型数字,根据 IEEE 754 标准的浮点数算法编码为双精度浮点数。该标准类似于用符号数值表示法来编码整数,所以也会出现“-0”。当你要表示一个数字时,他可以表示一个小到与“0”区别不出来的数,因为编码方式无法足够精确的表示这种区别。用“-0”便可以记录一个数字在被认为是“0”之前,是“从坐标轴的那个方向”趋近真正的“0”的。关于“-0”的优劣,维基百科做了很好的总结。

引用
It is claimed that the inclusion of signed zero in IEEE 754 makes it much easier to achieve numerical accuracy in some critical problems, in particular when computing with complex elementary functions. On the other hand, the concept of signed zero runs contrary to the general assumption made in most mathematical fields (and in most mathematics courses) that negative zero is the same thing as zero. Representations that allow negative zero can be a source of errors in programs, as software developers do not realize (or may forget) that, while the two zero representations behave as equal under numeric comparisons, they are different bit patterns and yield different results in some operations.
引用
在 IEEE 754 标准中使用“-0”,更容易解决复杂数学计算中的精度等关键问题。另一方面,“-0”的概念与大多数数学领域(和数学课程)中的数学假设背道而驰,因为“-0”和“+0”是相同的。允许“-0”的存在,开发人员可能会没有认识到(或忘了)这一点,由于两个“0”的二进制表示不同,“-0”的存在,在某些计算中会产生不同的结果,导致判断两个数字相等的代码可能隐含一些错误。 
此段维基百科引用的英文没有对应的中文版,所以自己做了翻译。

JavaScript goes to some lengths to hide the fact that there are two zeros. 
JavaScript 做了很多工作来隐藏有两个“0”的事实。

2.Hiding the zero’s sign 
2.隐藏“0”的符号
 
In JavaScript, you’ll usually write 0, which always means +0. But it also displays −0 simply as 0. The following is what you see when you use any browser command line or the Node.js REPL: 
通常认为,JavaScript 中显示的“0”,表示的都是“+0”。其实“-0”也直接显示为“0”,下面的例子显示了浏览器命令行和Node.js中的执行情况:

  1. > -0
  2. 0

The reason is that the standard toString() method converts both zeros to the same "0". 
原因是按照规则两个“0”都通过调用 toString() 方法转换成了相同的结果“0”:

  1. > (-0).toString()
  2. '0'
  3. > (+0).toString()
  4. '0'

The illusion of a single zero is also perpetrated by the equals operators. Even strict equality considers both zeros the same, making it very hard to tell them apart (in the rare case that you want to). 
等于“==”操作符同样这样对待“-0”,甚至全等符号“===”也判断为他们相等,这使他们很难被区别(但在某些情况下需要区分)。

  1. > +0 === -0
  2. true

The same holds for the less-than and greater-than operators – they consider both zeros equal. 
大于“>”小于“<”符号同样判断两个“0”相等。

  1. > -0 < +0
  2. false
  3. > +0 < -0
  4. false

3.Where the zero’s sign matters 
3.“0”的符号都影响了哪些地方
 
The sign of the 0 rarely influences results of computations. And +0 is the most common 0. Only a few operations produce −0, most of them just pass an existing −0 through. This section shows a few examples where the sign matters. For each example, think about whether it could be used to tell −0 and +0 apart, a task that we will tackle in Sect. 4. In order to make the sign of a zero visible, we use the following function. 
“-0”在一些很罕见的地方影响计算结果。通常“+0”就是通常的“0”。只有少数运算产生“-0”的结果,大多数都直接忽略“-0”的存在。这一段将展示“-0”在哪些情况下产生影响。想一想下面每一个例子要区别“-0”和“+0”的原因,在展示过程中,为了能清晰的看到“-0”,我们将使用下面这个函数。

  1. function signed(x) {
  2. if (x === 0) {
  3. // isNegativeZero() will be shown later 【isNegativeZero() 将在后文中给出实现】
  4. return isNegativeZero(x) ? "-0" : "+0";
  5. } else {
  6. // Otherwise, fall back to the default 【其它情况下,使用默认方法】
  7. // We don’t use x.toString() so that x can be null or undefined 【null 或 undefined 不能使用 x.toString() 的写法】
  8. return Number.prototype.toString.call(x);
  9. }
  10. }

3.1.Adding zeros 
3.1.加法
 
Quoting Sect. 11.6.3 of the ECMAScript 5.1 specification, “Applying the Additive Operators to Numbers”:

引用
The sum of two negative zeros is −0. The sum of two positive zeros, or of two zeros of opposite sign, is +0.

For example:

引用 ECMAScript 5.1 规范第 11.6.3 节 “加法的变形” 的说明

引用
两个“-0”相加得“-0”。两个“+0”相加得“+0”,符号相反的两个“0”相加得“+0”

如下:

  1. > signed(-0 + -0)
  2. '-0'
  3. > signed(-0 + +0)
  4. '+0'

This doesn’t give you a way to distinguish the two zeros, because what comes out is as difficult to distinguish as what goes in. 
这并不能告诉你怎样区别两个“0”,因为运算的输入和输出一样难区别。

3.2.Multiplying by zero 
3.2.乘法
 
When multiplying with zero with a finite number, the usual sign rules apply: 
当两个非无穷数与“0”相乘时,就可以用通常的乘法规则了。

  1. > signed(+0 * -5)
  2. '-0'
  3. > signed(-0 * -5)
  4. '+0'

Multiplying an infinity with a zero results in NaN: 
无穷数与“0”相乘,结果为非数字(NaN)

  1. > -Infinity * +0
  2. NaN

3.3.Dividing by zero 
3.3.除法
 
You can divide any non-zero number (including infinities) by zero. The result is an infinity whose sign is subject to the usual rules. 
用任何非零数(包括无穷)来除以“0”。结果符合通常的符号规则。

  1. > 5 / +0
  2. Infinity
  3. > 5 / -0
  4. -Infinity
  5. > -Infinity / +0
  6. -Infinity

Note that -Infinity and +Infinity can be distinguished via ===. 
注意,正无穷和负无穷可以用“===”进行区别。

  1. > -Infinity === Infinity
  2. false

Dividing a zero by a zero results in NaN:“0”除以“0”为非数字(NaN)。

  1. > 0 / 0
  2. NaN
  3. > +0 / -0
  4. NaN

3.4.Math.pow() 
3.4.乘方运算
 
The following is a table of the results of Math.pow() if the first argument is zero: 
下表列出了以“0”为底数的乘法运算结果

  1. pow(+0, y<0) → +∞
  2. pow(−0, odd y<0) → −∞ //【奇数次幂】
  3. pow(−0, even y<0) → +∞ //【偶数次幂】

Interaction:

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

3.5.Math.atan2() 
3.5.极坐标弧度
 
The following is a table of the results that are returned if one of the arguments is zero. 
下表列出了目标点横纵坐标为零时的返回值

  1. atan2(+0, +0) → +0
  2. atan2(+0, −0) → +π
  3. atan2(−0, +0) → −0
  4. atan2(−0, −0) → −π
  5. atan2(+0, x<0) → +π
  6. atan2(−0, x<0) → −π

Hence, there are several ways to determine the sign of a zero. For example: 
因此,我们发现了区分两个零的方法,如:

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

atan2 is one of the few operations that produces −0 for non-zero arguments: 
atan2 是少数几个能用非零参数产生“-0”的运算之一

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

Therefore:因此

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

3.6.Math.round() 
3.6.四舍五入
 
Math.round() is another operation that returns −0 for arguments other than −0 and +0: 
Math.round()是另一个不用“-0”和“0”能产生“-0”的运算。

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

Here we have the effect that we talked about at the beginning: The sign of the zero records the sign of the value before rounding, “from which side” we approached 0. 
现在我们可以体会前文中【用“-0”便可以记录一个数字在被认为是“0”之前,是“从坐标轴的那个方向”趋近真正的“0”的。】的含义了。

4.Telling the two zeros apart 
4.区分两个“0”
 
The canonical solution for determining the sign of a zero is to divide one by it and then check whether the result is -Infinity or +Infinity: 
一个典型的辨别“0”的符号的方法,就是检查用“1”除以它的运算结果是正无穷还是负无穷:

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

The above sections showed several other options. One original solution comes from Allen Wirfs-Brock. Here is a slightly modified version of it: 
前文也展示了另外一些选择。Allen Wirfs-Brock 还提供了一种基于对象原型的方法,这里有一个稍作修改的版本。

  1. function isNegativeZero(x) {
  2. if (x !== 0) return false;
  3. var obj = {};
  4. Object.defineProperty(obj, 'z', { value: -0, configurable: false });
  5. try {
  6. // Is x different from z’s previous value? Then throw exception.【如果 x 与前面定义的 z 取值不同,则会抛出异常。】
  7. Object.defineProperty(obj, 'z', { value: x });
  8. } catch (e) {
  9. return false
  10. };
  11. return true;
  12. }

Explanation: In general, you cannot redefine a non-configurable property – an exception will be thrown. For example: 
说明:通常,你不能重定义一个“不可配置”(non-configurable)的属性,如果这么做会抛出下面这个异常:

  1. TypeError: Cannot redefine property: z

However, JavaScript will ignore your attempt if you use the same value as the existing one. In this case, whether a value is the same is not determined via ===, but via an internal operation that distinguishes −0 and +0. You can read up on the details in Wirfs-Brock’s blog post (freezing an object makes all properties non-configurable). 
JavaScript 试图会忽略你对用相同的值进行的修改。在这个例子中,值相同并不是通过全等运算符“===”来判断的,是通过一种内在的机制来区分“-0”和“+0”的。详细内容可以去读 Wirfs-Brock 的博文“设置所有属性为不可配置实现对象锁定(freezing an object makes all properties non-configurable)

5.Conclusion 
5.结论
 
We have seen that there are two zeros, because of how the sign is encoded for JavaScript’s numbers. However, −0 is normally hidden and it’s best to pretend that there is only one zero. Especially, because the difference between the zeros has little bearing on computations. Even strict equality === can’t tell them apart. Should you, against all expectations or just for fun, need to determine the sign of a zero, there are several ways to do so. Note that the slightly quirky existence of two zeros is not JavaScript’s fault, it simply follows the IEEE 754 standard for floating point numbers. 
我们已经看到两个零的符号在 JavaScript中是怎样编码的。虽然通常情况下“-0”被隐藏的很好,伪装成就像只有一个“0”存在的样子。特别是一些运算掩盖了这点小小的不同。使全等“===”操作也无法区别他们。如果你想要区分两个“0”,不管是有意为之,还是只为了好玩儿,这里已经提供了一些方法。要注意,有两个“0”这一点儿古怪之处,并不是 JavaScript 的bug,而是遵从了 IEEE 754 规范的浮点数规则。

6.Related reading 
6.扩展阅读
 
This post is part of a series on JavaScript numbers that comprises the following posts:

  • Integers and shift operators in JavaScript
  • Displaying numbers in JavaScript

Furthermore, the blog post “Stricter equality in JavaScript” examines that === cannot detect either the value NaN or the sign of a zero. 
本文是几篇讨论 JavaScript 中数字的系列文章中的一篇,系列的其他文章在下面:

另外,JavaScript中的严格比较研究了“===”不能用于检查 NaN 或 “-0” 的各种情况。

JavaScript中的两个“0” -0和+0的更多相关文章

  1. Java与JavaScript中判断两字符串是否相等的区别

    JavaScript是一种常用的脚本语言,这也决定了其相对于其他编程语言显得并不是很规范.在JavaScript中判断两字符串是否相等 直接用==,这与C++里的String类一样.而Java里的等号 ...

  2. JavaScript中的两个“0”(翻译)

    本文翻译自JavaScript’s two zeros JavaScript has two zeros: −0 and +0. This post explains why that is and ...

  3. Javascript中的undefined、null、""、0值和false的区别总结

    在程序语言中定义的各种各样的数据类型中,我们都会为其定义一个"空值"或"假值",比如对象类型的空值null,.NET Framework中数据库字段的空值DBN ...

  4. JavaScript中的ParseInt("08")和“09”返回0的原因分析及解决办法

    今天在程序中出现一个bugger ,调试了好久,最后才发现,原来是这个问题. 做了一个实验: alert(parseInt("01")),当这个里面的值为01====>07时 ...

  5. Javascript中那些你不知道的事之-- false、0、null、undefined和空字符串

    话不多说直接进入主题:(如果有写的不对的地方欢迎指正) 我们先来看看他们的类型分别是什么: typeof类型检测结果 结论:false是布尔类型对象,0是数字类型对象,null是object对象,un ...

  6. JavaScript中登录名的正则表达式及解析(0基础)

    简言 在JavaScript中,经常会用到正则表达式来进行模式匹配.例如,登录名验证,密码强度验证,字符串查找或替换等操作.现在就开始吧,零基础写出你的第一个正则表达式! 在做用户注册时,都会用到登录 ...

  7. javascript中对两个对象进行排序 和 java中的两个对象排序

    javascript中的对象数组排序 一 定义一个对象数组 var text = [{"name":"张","age":24},{" ...

  8. JavaScript中的两种全局对象

    这里总结的东西特别适合先学习c/c++, Java这类标准语言再学JS的童鞋们看,因为JS在程序执行之前就会初始化一个全局对象,这个全局对象到底是什么是跟JS程序运行环境有关的. 根据JavaScri ...

  9. javascript中对象两种创建方式

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

随机推荐

  1. Swift学习笔记四:数组和字典

      Swift 提供两种集合类型来存储集合,数组和字典. 数组是一个同类型的序列化列表集合.字典是一个能够使用相似于键的唯一标识符来获取值的非序列化集合.也就是说数组是有序的.字典是无序的. 一. 数 ...

  2. Python和数据科学的起步指南

    http://python.jobbole.com/80853/ Python拥有着极其丰富且稳定的数据科学工具环境.遗憾的是,对不了解的人来说这个环境犹如丛林一般(cue snake joke).在 ...

  3. Dora.Interception, 为.NET Core度身打造的AOP框架[3]:Interceptor的注册

    在<不一样的Interceptor>中我们着重介绍了Dora.Interception中最为核心的对象Interceptor,以及定义Interceptor类型的一些约定.由于Interc ...

  4. google protobuf使用2

    protobuf mutable_* 函数 从该函数的实现上来看,该函数返回指向该字段的一个指针.同时将该字段置为被设置状态. 若该对象存在,则直接返回该对象,若不存在则新new 一个.

  5. python16_day04【编码、函数、装饰器、包】

    一.编码总结 """python2 文件存储默认是ascii方式,启动加#coding:utf8就是文件以utf8方式打开.否则就是以ascii.变量则是str. 例子: ...

  6. Python高阶函数-闭包

    高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回. 在这里我们首先回忆一下python代码运行的时候遇到函数是怎么做的. 从python解释器开始执行之后,就在内存中开辟了一个空间 每当 ...

  7. Delphi 正则表达式语法(4): 常用转义字符与 .

    Delphi 正则表达式语法(4): 常用转义字符与 . // \d 匹配所有数字, 相当于 [0-9] var   reg: TPerlRegEx; begin   reg := TPerlRegE ...

  8. LeetCode:二进制手表【401】

    LeetCode:二进制手表[401] 题目描述 二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59). 每个 LED 代表一个 0 或 1,最低位在右 ...

  9. HDU - 1695 GCD (容斥+枚举)

    题意:求区间1<=i<=b与区间1<=j<=d之间满足gcd(i,j) = k 的数对 (i,j) 个数.(i,j)与(j,i) 算一个. 分析:gcd(i,j)=k可以转化为 ...

  10. POJ - 3662 Telephone Lines (Dijkstra+二分)

    题意:一张带权无向图中,有K条边可以免费修建.现在要修建一条从点1到点N的路,费用是除掉免费的K条边外,权值最大的那条边的值,求最小花费. 分析:假设存在一个临界值X,小于X的边全部免费,那么此时由大 ...