JavaScript中的两个“0”(翻译)
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”的优劣,维基百科做了很好的总结。
此段维基百科引用的英文没有对应的中文版,所以自己做了翻译。
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中的执行情况:
- > -0
- 0
The reason is that the standard toString() method converts both zeros to the same "0".
原因是按照规则两个“0”都通过调用 toString() 方法转换成了相同的结果“0”:
- > (-0).toString()
- '0'
- > (+0).toString()
- '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”,甚至全等符号“===”也判断为他们相等,这使他们很难被区别(但在某些情况下需要区分)。
- > +0 === -0
- true
The same holds for the less-than and greater-than operators – they consider both zeros equal.
大于“>”小于“<”符号同样判断两个“0”相等。
- > -0 < +0
- false
- > +0 < -0
- 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”,我们将使用下面这个函数。
- function signed(x) {
- if (x === 0) {
- // isNegativeZero() will be shown later 【isNegativeZero() 将在后文中给出实现】
- return isNegativeZero(x) ? "-0" : "+0";
- } else {
- // Otherwise, fall back to the default 【其它情况下,使用默认方法】
- // We don’t use x.toString() so that x can be null or undefined 【null 或 undefined 不能使用 x.toString() 的写法】
- return Number.prototype.toString.call(x);
- }
- }
3.1.Adding zeros
3.1.加法
Quoting Sect. 11.6.3 of the ECMAScript 5.1 specification, “Applying the Additive Operators to Numbers”:
For example:
引用 ECMAScript 5.1 规范第 11.6.3 节 “加法的变形” 的说明
如下:
- > signed(-0 + -0)
- '-0'
- > signed(-0 + +0)
- '+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”相乘时,就可以用通常的乘法规则了。
- > signed(+0 * -5)
- '-0'
- > signed(-0 * -5)
- '+0'
Multiplying an infinity with a zero results in NaN:
无穷数与“0”相乘,结果为非数字(NaN)
- > -Infinity * +0
- 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”。结果符合通常的符号规则。
- > 5 / +0
- Infinity
- > 5 / -0
- -Infinity
- > -Infinity / +0
- -Infinity
Note that -Infinity and +Infinity can be distinguished via ===.
注意,正无穷和负无穷可以用“===”进行区别。
- > -Infinity === Infinity
- false
Dividing a zero by a zero results in NaN:“0”除以“0”为非数字(NaN)。
- > 0 / 0
- NaN
- > +0 / -0
- 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”为底数的乘法运算结果
- pow(+0, y<0) → +∞
- pow(−0, odd y<0) → −∞ //【奇数次幂】
- pow(−0, even y<0) → +∞ //【偶数次幂】
Interaction:
- > Math.pow(+0, -1)
- Infinity
- > Math.pow(-0, -1)
- -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.
下表列出了目标点横纵坐标为零时的返回值
- atan2(+0, +0) → +0
- atan2(+0, −0) → +π
- atan2(−0, +0) → −0
- atan2(−0, −0) → −π
- atan2(+0, x<0) → +π
- atan2(−0, x<0) → −π
Hence, there are several ways to determine the sign of a zero. For example:
因此,我们发现了区分两个零的方法,如:
- > Math.atan2(-0, -1)
- -3.141592653589793
- > Math.atan2(+0, -1)
- 3.141592653589793
atan2 is one of the few operations that produces −0 for non-zero arguments:
atan2 是少数几个能用非零参数产生“-0”的运算之一
- atan2(y>0, +∞) → +0
- atan2(y<0, +∞) → −0
Therefore:因此
- > signed(Math.atan2(-1, Infinity))
- '-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”的运算。
- > signed(Math.round(-0.1))
- '-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”除以它的运算结果是正无穷还是负无穷:
- function isNegativeZero(x) {
- return x === 0 && (1/x < 0);
- }
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 还提供了一种基于对象原型的方法,这里有一个稍作修改的版本。
- function isNegativeZero(x) {
- if (x !== 0) return false;
- var obj = {};
- Object.defineProperty(obj, 'z', { value: -0, configurable: false });
- try {
- // Is x different from z’s previous value? Then throw exception.【如果 x 与前面定义的 z 取值不同,则会抛出异常。】
- Object.defineProperty(obj, 'z', { value: x });
- } catch (e) {
- return false
- };
- return true;
- }
Explanation: In general, you cannot redefine a non-configurable property – an exception will be thrown. For example:
说明:通常,你不能重定义一个“不可配置”(non-configurable)的属性,如果这么做会抛出下面这个异常:
- 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”(翻译)的更多相关文章
- JavaScript中的两个“0” -0和+0
JavaScript中的两个“0”(翻译) 本文翻译自JavaScript’s two zeros JavaScript has two zeros: −0 and +0. This post e ...
- Java与JavaScript中判断两字符串是否相等的区别
JavaScript是一种常用的脚本语言,这也决定了其相对于其他编程语言显得并不是很规范.在JavaScript中判断两字符串是否相等 直接用==,这与C++里的String类一样.而Java里的等号 ...
- Javascript 中的false、0、null、undefined和空字符串对象
在Javascript中,我们经常会接触到题目中提到的这5个比较特别的对象——false.0.空字符串.null和undefined.这几个对象很容易用错,因此在使用时必须得小心. 类型检测 我们下来 ...
- javascript中的undefined,null,"",0和false的云集
在各种各样的数据类型中,我们都会为其定义一个"空值"或"假值",比如对象类型的空值null,.NET Framework中数据库字段的空值DBNull,bool ...
- JavaScript中的两种全局对象
这里总结的东西特别适合先学习c/c++, Java这类标准语言再学JS的童鞋们看,因为JS在程序执行之前就会初始化一个全局对象,这个全局对象到底是什么是跟JS程序运行环境有关的. 根据JavaScri ...
- javascript中对两个对象进行排序 和 java中的两个对象排序
javascript中的对象数组排序 一 定义一个对象数组 var text = [{"name":"张","age":24},{" ...
- javascript中对象两种创建方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- null的坑 和 比较运算符、相等运算符的隐式转换问题 (在javascript中,null>=0 为真,null<=0 为真,null==0却为假,null到底是什么?)
null在关系运算中的坑 & 关系运算符的隐式转换问题 注意: 比较运算符 和 相等运算符 的 ECMAscript 语法实现不同. 比较运算符 和 相等运算符 对数据进行了隐式转换, 相当于 ...
- javascript中的两个定时函数setTimeOut()和setInterVal()的区别
js中经常性要用到间隔几秒或暂停几秒执行某个函数, 简单介绍我从网上收集到setTimeOut()和setInterVal()的区别1.setInterVal()介绍 1)定义 setInterval ...
随机推荐
- angularjs中的interval定时执行功能
一个例子,用来显示当前实时时间,1秒钟刷新一次: <!DOCTYPE html> <html ng-app="myApp"> <head> &l ...
- Oracle DB 复制数据库
• 列出创建副本数据库的目的 • 选择用于复制数据库的方法 • 使用RMAN 复制数据库 • 使用RMAN 备份复制数据库 • 基于正在运行的实例复制数据库 使用副本数据库 • 使用副本数据库可执行以 ...
- <c:redirect>标签的使用
<c:redirect>标签的使用代码例子 redirect.jsp <%@ page contentType="text/html" pageEncoding= ...
- 手动脱UPX 壳实战
作者:Fly2015 Windows平台的加壳软件还是比較多的,因此有非常多人对于PC软件的脱壳乐此不彼,本人菜鸟一枚,也学习一下PC的脱壳.要对软件进行脱壳.首先第一步就是 查壳.然后才是 脱壳. ...
- 查看80port是否被占用
windows下命令: netstat -ano|findstr "0.0:80"
- 【LeetCode】94. Binary Tree Inorder Traversal (3 solutions)
Binary Tree Inorder Traversal Given a binary tree, return the inorder traversal of its nodes' values ...
- EMQ ---v2.3.11源码成熟度
从原作者那边了解到,总体还可以,但是做不到99.99%稳定.主要是连接内存占用没有保护. pubsub均衡时很稳定,但是集群或大量消息向少量订阅发布时会崩溃,小概率情况. EMQ中CPU是公平分配给M ...
- 四种对象生存期和作用域、static 用法总结
一.四种对象生存期和作用域 栈对象 隐含调用构造函数(程序中没有显式调用) 堆对象 隐含调用构造函数(程序中没有显式调用),要显式释放 全局对象.静态全局对象 全局对象的构造先于main函数 已初始化 ...
- Makefile生成器,使用C++和Boost实现
今天学习了一下Boost的文件遍历功能,同一时候发现GNU编译器有-MM选项.能够自己主动生成依赖关系,于是利用以上两点写了一个Makefile生成器. 能够生成一般的单个可运行文件的Makefile ...
- SIFT 、Hog 、LBP 了解
SIFT.HOG.LBP,这三者都属于局部特征. 一.三者原理上的区别 1.SIFT:Scale-Invariant Feature Taransform,尺度不变特征变换. 尺度空间的极值检测:搜索 ...