V8 使用“常量折疊”優(yōu)化技巧沃琅,導(dǎo)致冪(**)運算有時候不等于 Math.pow()

在如今的主流 Web 編程語言中,如 PHP 或 Python 等究抓,都包含冪運算符(一般來說符號是 ^ 或者 **)猾担。而最新的 ES7 中也增加了對冪運算的支持,使用符號 **刺下,最新的 Chrome 已經(jīng)提供了對冪運算的支持垒探。

但是在 javascript 中,** 運算有時候并不等于 Math.pow(a,b)怠李,在最新的 Chrome 55 中:

Math.pow(99,99) 的結(jié)果是 3.697296376497263e+197,

但是 99**99 的結(jié)果是 3.697296376497268e+197蛤克。

兩者并不相等

3.697296376497263e+197
3.697296376497268e+197

而且 Math.pow(99,99) - 99**99 的結(jié)果也不是 0 而是 -5.311379928167671e+182捺癞。

因此我們猜測,** 操作符只是冪運算的另一個實現(xiàn)构挤。但是當(dāng)我們寫一個函數(shù)時髓介,冪運算又表現(xiàn)出詭異的特性:

function diff(x) {
  return Math.pow(x,x) - x**x;
}

調(diào)用 diff(99) 返回 0。WTF筋现?兩者又相等了唐础!

猜猜下面代碼輸出什么?

var x = 99;
x**x - 99**99;

這段代碼的運行結(jié)果是 -5.311379928167671e+182矾飞。


這簡直是薛定諤的冪一膨。

究其原因,V8 引擎使用了常量折疊(const folding)洒沦。常量折疊是一種編譯器的編譯優(yōu)化技術(shù)豹绪。

考慮如下代碼:

for (let i = 0; i < 100*100*100; i++){
  // 循環(huán)體
}

該循環(huán)的條件 i<100*100*100 是一個表達(dá)式(expression),如果放到判斷時再求值那么 100*100*100 的計算將會進(jìn)行 1000000 次申眼。如果編譯器在語法分析階段進(jìn)行常量合并瞒津,該循環(huán)將會變?yōu)檫@樣:

for (let i = 0; i < 1000000; i++){
  // 循環(huán)體
}

而上文中提到的 99**99 的計算也使用到了常量折疊。也就是說 99**99 是在編譯時進(jìn)行計算(常量折疊)括尸,而 Math.pow 總是在運行時進(jìn)行計算巷蚪。當(dāng)我們使用變量進(jìn)行冪運算時(例 a**b)此時不存在常量折疊,因此 a ** b 的值在運行時進(jìn)行計算濒翻,** 會被編譯成 Math.pow 調(diào)用屁柏。

在源碼 src/parsing/parser.cc 文件中啦膜,編譯時計算代碼:

case Token::EXP: {
double value = Pow(x_val, y_val);
int int_value = static_cast<int>(value);
*x = factory()->NewNumberLiteral(
    int_value == value && value != -0.0 ? int_value : value, pos,
    has_dot);
return true;

可以看到使用了 Pow 函數(shù)計算了冪運算的求值結(jié)果。Pow 是一個 inline 的函數(shù)前联,內(nèi)部做了一些常規(guī)優(yōu)化功戚,對不能優(yōu)化的情況則使用了 std::pow(x, y) 來計算最終結(jié)果。

Math.pow 的算法為:

// ES6 section 20.2.2.26 Math.pow ( x, y )
TF_BUILTIN(MathPow, CodeStubAssembler) {
  Node* x = Parameter(1);
  Node* y = Parameter(2);
  Node* context = Parameter(5);
  Node* x_value = TruncateTaggedToFloat64(context, x);
  Node* y_value = TruncateTaggedToFloat64(context, y);
  Node* value = Float64Pow(x_value, y_value);
  Node* result = ChangeFloat64ToTagged(value);
  Return(result);
}

可見兩者使用了不同的算法似嗤。但是當(dāng)不做常量折疊的時候啸臀,** 則轉(zhuǎn)換成了 Math.pow 函數(shù)調(diào)用:

Expression* Parser::RewriteExponentiation(
    Expression* left, 
    Expression* right,
    int pos) {
  ZoneList<Expression*>* args = new (zone()) ZoneList<Expression*>(2, zone());
  args->Add(left, zone());
  args->Add(right, zone());
  return factory()->NewCallRuntime(Context::MATH_POW_INDEX, args, pos);
}

于是就造成了 ** 有時不等于 Math.pow 的怪異問題。再看看如下代碼:

console.log(99**99);
a = 99, b = 99;
console.log(a**b);
console.log(Math.pow(99, 99));

分別輸出:

3.697296376497268e+197
3.697296376497263e+197
3.697296376497263e+197

其實

9999=369729637649726772657187905628805440595668764281741102430259972423552570455277523421410650010128232727940978889548326540119429996769494359451621570193644014418071060667659301384999779999159200499899

因此第一個結(jié)果更接近準(zhǔn)確的值烁落。

上周(2017年1月16日)這個怪異的行為已經(jīng)作為一個 bug 提交給了 V8 項目乘粒,bug 編號 #5848。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伤塌,一起剝皮案震驚了整個濱河市灯萍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌每聪,老刑警劉巖旦棉,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異药薯,居然都是意外死亡绑洛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門童本,熙熙樓的掌柜王于貴愁眉苦臉地迎上來真屯,“玉大人,你說我怎么就攤上這事穷娱“竽瑁” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵泵额,是天一觀的道長配深。 經(jīng)常有香客問我,道長嫁盲,這世上最難降的妖魔是什么凉馆? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮亡资,結(jié)果婚禮上澜共,老公的妹妹穿的比我還像新娘。我一直安慰自己锥腻,他們只是感情好嗦董,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘦黑,像睡著了一般京革。 火紅的嫁衣襯著肌膚如雪奇唤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天匹摇,我揣著相機與錄音咬扇,去河邊找鬼。 笑死廊勃,一個胖子當(dāng)著我的面吹牛懈贺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坡垫,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼梭灿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冰悠?” 一聲冷哼從身側(cè)響起堡妒,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溉卓,沒想到半個月后皮迟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡桑寨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年伏尼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片西疤。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖休溶,靈堂內(nèi)的尸體忽然破棺而出代赁,到底是詐尸還是另有隱情,我是刑警寧澤兽掰,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布芭碍,位于F島的核電站,受9級特大地震影響孽尽,放射性物質(zhì)發(fā)生泄漏窖壕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一杉女、第九天 我趴在偏房一處隱蔽的房頂上張望瞻讽。 院中可真熱鬧,春花似錦熏挎、人聲如沸速勇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烦磁。三九已至养匈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間都伪,已是汗流浹背呕乎。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留陨晶,地道東北人猬仁。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像珍逸,于是被迫代替她去往敵國和親逐虚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

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