寫于2015年5月8日礁芦,最新修訂于2015年5月26日,可能已過時宴偿,請謹慎參考窄刘。
自古js多奇葩,語言層面上有許多坑活翩,入坑多了也就習(xí)慣了翻伺。那就再多一個坑吧。
javascript在判斷兩個值是否相等時拉宗,有兩種方式==
和===
。這兩者的區(qū)別我就不多說了魁巩,隨便一本js書上都有姐浮,總之一般情形下我們有這樣的結(jié)論:==
省事,但結(jié)果混亂肾扰,很多情形下近乎偽科學(xué)蛋逾,不建議使用,很多人更是視其為洪水猛獸甩恼,避之不及(它的坑太多沉颂,我寫不完铸屉,不寫了);===
很嚴謹彻坛,在絕大多數(shù)情形下昌屉,應(yīng)該使用。這個結(jié)論我是很認同的躬厌,并且盡量這么做竞帽。但是,javascript作為一門任性的語言疙渣,不打打臉怎么好玩呢堆巧。那么一起來愉快地玩壞===
吧 泼菌。
要玩壞===
灶轰,只需要用到0刷钢。沒錯乳附,就是數(shù)字0赋除。在javascript中,數(shù)字都是以浮點數(shù)的形式參與運算腌且,其編碼規(guī)則遵循IEEE_754標準(0.2+0.1不等于0.3這個問題怪它0湓恪)棱貌。重點也不是這個標準,重點是按照這個標準今魔,數(shù)字編碼會有一位符號位表示正負障贸,所以對于任何數(shù)字,非正即負涩维。那么問題來了嘀粱,0呢锋叨?答案是0也是有正負的。通常我們看到的薄湿、定義的0都是+0,但在javascript中-0也是存在的吆倦。而在實際運算中坐求,某些場景下,計算結(jié)果會產(chǎn)生+0和-0的差異须妻;同樣+0和-0參與計算時泛领,可能會導(dǎo)致不同的結(jié)果渊鞋。但在直觀感受上,很明顯+0和-0應(yīng)該是相等的才對儡湾,于是javascript在語言層面上想消除這種差異员辩,所以:
看起來很合理奠滑,雖然有點奇怪。但是再看這樣的運算:
這不科學(xué)摊崭,明明判定為完全相同的值呢簸,進行相同的運算后乏屯,結(jié)果會不相等。對于開發(fā)者而言蛤迎,我們并不能在任何場景下信任
===
含友,它也有不靠譜的時候。
應(yīng)對這種“不科學(xué)”的情形也很簡單:
function isEqual(a, b){
if (a !== b) return false;
return a !== 0 || 1 / a === 1 / b;
}
2015年5月26日補充:
關(guān)于IEEE_754標準
這是一個使用二進制表示浮點數(shù)的方案宜咒,應(yīng)用很廣泛把鉴。它規(guī)定了一位符號位表示正負,0也不例外倍阐,這是負0產(chǎn)生的原因逗威。這是帶符號位的浮點數(shù)表示方案的通病凯旭,當(dāng)然使套,不帶符號位的方案就可以避免這個問題侦高。不過這個問題并不嚴重,通常程序語言并不希望開發(fā)者知道負0的存在计螺,直接在語言層面上規(guī)定正0和負0相等瞧壮,這才是+0 === -0
的本質(zhì)原因。
我說負0的問題并不嚴重陈轿,是因為其使用場景少秦忿,出bug機率低灯谣。說到不嚴重,肯定有嚴重的問題半等,那就是浮點數(shù)精度的問題,數(shù)值是精確的莽囤、連續(xù)的切距;而數(shù)值編碼是離散的谜悟,很多時候不準確的。畢竟32位也好最筒、64位也好蔚叨,能表現(xiàn)的浮點數(shù)是有限的蔑水。從0.1、0.2到0.9丹擎,真正能精確表達的只有0.5歇父,其他的數(shù)字都是近似值。你可以自己嘗試一下毁渗,不管js灸异、java還是c++羔飞,浮點數(shù)運算從來不可靠,比如0.2 + 0.1并不等于0.3么伯。如果你有過c++或者java編程經(jīng)驗田柔,很可能接觸過一些奇葩的代碼來處理浮點數(shù)比較,比如定義一個精度0.002f
(假設(shè))欣舵,如果abs(floatA - floatB) < 0.002f
缀磕,則認為兩者相等。很反人類袜蚕,但沒辦法糟把。編程語言有錯嗎?沒有牲剃,但現(xiàn)實就是要妥協(xié)遣疯。
關(guān)于負0
負0在數(shù)學(xué)上并沒有意義,0是無符號的颠黎。但如果一個數(shù)值趨向于0另锋,那么它是有符號的,可以為負狭归。但對于這種情況,IEEE_754標準并沒有定義文判。所以實際開發(fā)場景中过椎,如果一個數(shù)值趨向于0戏仓,那么它就是0疚宇,此時,負0就有意義了赏殃,它可能代表的是趨向于0的負數(shù)敷待。本質(zhì)上這還是IEEE_754精度,或者表達范圍的問題仁热。但當(dāng)負0有了具體意義的時候榜揖,再說+0 === -0
,我覺得有待商榷的抗蠢。
負0常見嗎
首先我要說負0不常見举哟,但絕不是大家想的通常不可能出現(xiàn)。其實一些常見的迅矛、簡單的場景下就有可能出現(xiàn)-0妨猩。比如Math.ceil(-0.1)
、Math.round(-0.1)
秽褒;還有不常見Math.atan2(-1, Infinity)
等壶硅。由正負0而產(chǎn)生不同計算結(jié)果的操作相對會更多一點威兜,比如文章中的舉例的倒數(shù)運算。
參考資料:
javascript與===運算
通常情況下庐椒,===在js中椒舵,表示判斷類型和值是否都完全相等。都說通常了扼睬,肯定有反例逮栅。很多熟悉js的人都知道這樣一個知識點,NaN!==NaN
窗宇。所以我們常炒敕ィ可以看到這樣的代碼:
function isNaN (num) {
return num !== num;
}
這就是編程語言為了滿足直觀的理解而操縱運算符的結(jié)果。+0和-0同樣是這樣军俊,它們的編碼并不同侥加,但卻判定它們相等。
對于以上兩個點粪躬,EmacScript 6中加入了Object.is方法來處理:
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 0 === -0, but they are not identical
return 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") => true
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
});
參考資料:
- http://wiki.ecmascript.org/doku.php?id=harmony:egal
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
對于負0的問題担败,EmacScript 5中同樣加入了isNegative0來處理-0。
參考資料:
不僅如此镰官,一些工具類庫中也加入了類似的處理提前,如underscore的isEqual方法。
So…
對于絕大部分開發(fā)場景而言泳唠,-0根本沒有存在感狈网;但我把這個點分享出來,讓更多的人知道有-0這個東西笨腥,讓更多的人知道可能存在看似相同的輸入拓哺,經(jīng)過相同的計算,產(chǎn)生完全不同結(jié)果的可能脖母,避免他們遭遇奇怪的bug士鸥。