1. 程式人生 > >JavaScript中的兩個“0” -0和+0

JavaScript中的兩個“0” -0和+0

Coding spl comment nal inf exp lai 數值 other

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中整型和轉換操作
  • JavaScript中的數字顯示操作

另外,JavaScript中的嚴格比較研究了“===”不能用於檢查 NaN 或 “-0” 的各種情況。

JavaScript中的兩個“0” -0和+0