“===” 也有不靠谱的时候
寫于2015年5月8日,最新修訂于2015年5月26日,可能已過時,請謹慎參考。
原文
自古js多奇葩,語言層面上有許多坑,入坑多了也就習慣了。那就再多一個坑吧。
javascript在判斷兩個值是否相等時,有兩種方式 ==?和 ===。這兩者的區別我就不多說了,隨便一本 js 書上都有,總之一般情形下我們有這樣的結論:== 省事,但結果混亂,很多情形下近乎偽科學,不建議使用,很多人更是視其為洪水猛獸,避之不及(它的坑太多,我寫不完,不寫了);=== 很嚴謹,在絕大多數情形下,應該使用。這個結論我是很認同的,并且盡量這么做。但是,javascript 作為一門任性的語言,不打打臉怎么好玩呢。那么一起來愉快地玩壞 === 吧 。
要玩壞 ===,只需要用到0。沒錯,就是數字0。在 javascript 中,數字都是以浮點數的形式參與運算,其編碼規則遵循 IEEE_754 標準(不等于0.3這個問題怪它!)。重點也不是這個標準,重點是按照這個標準,數字編碼會有一位符號位表示正負,所以對于任何數字,非正即負。那么問題來了,0呢?答案是0也是有正負的。通常我們看到的,它義的0都是+0,但在 javascript 中-0也是存在的。而在實際運算中,某些場景下,計算結果會產生+0和-0的差異;同樣+0和-0參與計算時,可能會導致不同的結果。但在直觀感受上,很明顯+0和-0應該是相等的才對,于是 javascript 在語言層面上想消除這種差異,所以:
看起來很合理,雖然有點奇怪。但是再看這樣的運算:
這不科學,明明判定為完全相同的值,進行相同的運算后,結果會不相等。對于開發者而言,我們并不能在任何場景下信任 ===,它也有不靠譜的時候。
應對這種“不科學”的情形也很簡單:
function isEqual(a, b){if (a !== b) return false;return a !== 0 || 1 / a === 1 / b; } 復制代碼2015年5月14日補充:
強調一下本文的重點吧,我從來沒想質疑正負 Infinity 不相等的問題,我想分享的要點是:在 js 中,+0 === -0,但它們并不是完全相等的。
2015年5月26日補充:
頭一回回復評論比正文還長……集中整理一下吧。
關于 IEEE_754 標準
這是一個使用二進制表示浮點數的方案,應用很廣泛。它規定了一位符號位表示正負,0也不例外,這是負0產生的原因。這是帶符號位的浮點數表示方案的通病,當然,不帶符號位的方案就可以避免這個問題。不過這個問題并不嚴重,通常程序語言并不希望開發者知道負0的存在,直接在語言層面上規定正0和負0相等,這才是 +0 === -0 的本質原因。
我說負0的問題并不嚴重,是因為其使用場景少,出 bug 機率低。說到不嚴重,肯定有嚴重的問題,那就是浮點數精度的問題,數值是精確的、連續的;而數值編碼是離散的,很多時候不準確的。畢竟32位也好、64位也好,能表現的浮點數是有限的。從0.1、0.2到0.9,真正能精確表達的只有0.5,其他的數字都是近似值。你可以自己嘗試一下,不管 js、java 還是 c++,浮點數運算從來不可靠,比如 并不等于0.3。如果你有過 c++ 或者 java 編程經驗,很可能接觸過一些奇葩的代碼來處理浮點數比較,比如定義一個精度 0.002f(假設),如果 abs(floatA - floatB) < 0.002f,則認為兩者相等。很反人類,但沒辦法。編程語言有錯嗎?沒有,但現實就是要妥協。
關于負0
負0在數學上并沒有意義,0是無符號的。但如果一個數值趨向于0,那么它是有符號的,可以為負。但對于這種情況,IEEE_754 標準并沒有定義。所以實際開發場景中,如果一個數值趨向于0,那么它就是0,此時,負0就有意義了,它可能代表的是趨向于0的負數。本質上這還是 IEEE_754 精度,或者表達范圍的問題。但當負0有了具體意義的時候,再說 +0 === -0,我覺得有待商榷的。
負0常見嗎
首先我要說負0不常見,但絕不是大家想的通常不可能出現。其實一些常見的、簡單的場景下就有可能出現-0。比如 Math.ceil(-0.1)、Math.round(-0.1);還有不常見的 Math.atan2(-1, Infinity)?等。由正負0而產生不同計算結果的操作相對會更多一點,比如文章中的舉例的倒數運算。
參考資料:
- developer.mozilla.org/en-US/docs/…
javascript 與 === 運算
通常情況下,=== 在 js 中,表示判斷類型和值是否都完全相等。都說通常了,肯定有反例。很多熟悉js的人都知道這樣一個知識點,NaN !== NaN。所以我們常常可以看到這樣的代碼:
function isNaN (num){return num !== num; } 復制代碼這就是編程語言為了滿足直觀的理解而操縱運算符的結果。+0和-0同樣是這樣,它們的編碼并不同,但卻判定它們相等。
對于以上兩個點,EmacScript 6中加入了 Object.is 方法來處理:
Object.defineProperty(Object, 'is', {value: function(x, y) {if (x === y) {// 0 === -0, but they are not identicalreturn x !== 0 || 1 / x === 1 / y;}// NaN !== NaN, but they are identical.// NaNs are the only non-reflexive value, i.e., if x !== x,// then x is a NaN.// isNaN is broken: it converts its argument to number, so// isNaN("foo") => truereturn x !== x && y !== y;},configurable: true,enumerable: false,writable: true }); 復制代碼參考資料:
- wiki.ecmascript.org/doku.php?id…
- developer.mozilla.org/en-US/docs/…
對于負0的問題,EmacScript 5 中同樣加入了 isNegative0 來處理-0。
參考資料:
- www.wirfs-brock.com/allen/posts…
不僅如此,一些工具類庫中也加入了類似的處理,如 underscore 的 isEqual 方法。
So...
對于絕大部分開發場景而言,-0根本沒有存在感;但我把這個點分享出來,讓更多的人知道有-0這個東西,讓更多的人知道可能存在看似相同的輸入,經過相同的計算,產生完全不同結果的可能,避免他們遭遇奇怪的bug。
轉載于:https://juejin.im/post/5aa94e97f265da239a5f8394
總結
以上是生活随笔為你收集整理的“===” 也有不靠谱的时候的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MYSQL 生成UUID() 即 ORA
- 下一篇: BZOJ 2038: [2009国家集训