“===”也有不靠譜的時候

寫于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
});

參考資料:

對于負0的問題担败,EmacScript 5中同樣加入了isNegative0來處理-0。

參考資料:

不僅如此镰官,一些工具類庫中也加入了類似的處理提前,如underscore的isEqual方法。

So…

對于絕大部分開發(fā)場景而言泳唠,-0根本沒有存在感狈网;但我把這個點分享出來,讓更多的人知道有-0這個東西笨腥,讓更多的人知道可能存在看似相同的輸入拓哺,經(jīng)過相同的計算,產(chǎn)生完全不同結(jié)果的可能脖母,避免他們遭遇奇怪的bug士鸥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谆级,隨后出現(xiàn)的幾起案子烤礁,更是在濱河造成了極大的恐慌,老刑警劉巖哨苛,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸽凶,死亡現(xiàn)場離奇詭異,居然都是意外死亡建峭,警方通過查閱死者的電腦和手機玻侥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亿蒸,“玉大人凑兰,你說我怎么就攤上這事掌桩。” “怎么了姑食?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵波岛,是天一觀的道長。 經(jīng)常有香客問我音半,道長则拷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任曹鸠,我火速辦了婚禮煌茬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彻桃。我一直安慰自己坛善,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布邻眷。 她就那樣靜靜地躺著眠屎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肆饶。 梳的紋絲不亂的頭發(fā)上改衩,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音驯镊,去河邊找鬼燎字。 笑死,一個胖子當(dāng)著我的面吹牛阿宅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笼蛛,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼洒放,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滨砍?” 一聲冷哼從身側(cè)響起往湿,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惋戏,沒想到半個月后领追,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡响逢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年绒窑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舔亭。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡些膨,死狀恐怖蟀俊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情订雾,我是刑警寧澤肢预,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站洼哎,受9級特大地震影響烫映,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜噩峦,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一锭沟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧壕探,春花似錦冈钦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至导盅,卻和暖如春较幌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背白翻。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工乍炉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滤馍。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓岛琼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巢株。 傳聞我的和親對象是個殘疾皇子槐瑞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容