摘要:究其原因,引擎使用了常量折疊。常量折疊是一種編譯器的編譯優化技術。也就是說是在編譯時進行計算常量折疊,而總是在運行時進行計算。當我們使用變量進行冪運算時例此時不存在常量折疊,因此的值在運行時進行計算,會被編譯成調用。
在如今的主流 Web 編程語言中,如 PHP 或 Python 等,都包含冪運算符(一般來說符號是 ^ 或者 **)。而最新的 ES7 中也增加了對冪運算的支持,使用符號 **,最新的 Chrome 已經提供了對冪運算的支持。
但是在 javascript 中,** 運算有時候并不等于 Math.pow(a,b),在最新的 Chrome 55 中:
Math.pow(99,99) 的結果是 3.697296376497263e+197,
但是 99**99 的結果是 3.697296376497268e+197。
兩者并不相等
3.697296376497263e+197
3.697296376497268e+197
而且 Math.pow(99,99) - 99**99 的結果也不是 0 而是 -5.311379928167671e+182。
因此我們猜測,** 操作符只是冪運算的另一個實現。但是當我們寫一個函數時,冪運算又表現出詭異的特性:
function diff(x) { return Math.pow(x,x) - x**x; }
調用 diff(99) 返回 0。WTF?兩者又相等了!
猜猜下面代碼輸出什么?
var x = 99; x**x - 99**99;
這段代碼的運行結果是 -5.311379928167671e+182。
這簡直是薛定諤的冪。
究其原因,V8 引擎使用了常量折疊(const folding)。常量折疊是一種編譯器的編譯優化技術。
考慮如下代碼:
for (let i = 0; i < 100*100*100; i++){ // 循環體 }
該循環的條件 i<100*100*100 是一個表達式(expression),如果放到判斷時再求值那么 100*100*100 的計算將會進行 1000000 次。如果編譯器在語法分析階段進行常量合并,該循環將會變為這樣:
for (let i = 0; i < 1000000; i++){ // 循環體 }
而上文中提到的 99**99 的計算也使用到了常量折疊。也就是說 99**99 是在編譯時進行計算(常量折疊),而 Math.pow 總是在運行時進行計算。當我們使用變量進行冪運算時(例 a**b)此時不存在常量折疊,因此 a ** b 的值在運行時進行計算,** 會被編譯成 Math.pow 調用。
在源碼 src/parsing/parser.cc 文件中,編譯時計算代碼:
case Token::EXP: { double value = Pow(x_val, y_val); int int_value = static_cast(value); *x = factory()->NewNumberLiteral( int_value == value && value != -0.0 ? int_value : value, pos, has_dot); return true;
可以看到使用了 Pow 函數計算了冪運算的求值結果。Pow 是一個 inline 的函數,內部做了一些常規優化,對不能優化的情況則使用了 std::pow(x, y) 來計算最終結果。
而 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); }
可見兩者使用了不同的算法。但是當不做常量折疊的時候,** 則轉換成了 Math.pow 函數調用:
Expression* Parser::RewriteExponentiation( Expression* left, Expression* right, int pos) { ZoneList* args = new (zone()) ZoneList (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
因此第一個結果更接近準確的值。
上周(2017年1月16日)這個怪異的行為已經作為一個 bug 提交給了 V8 項目,bug 編號 #5848。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81363.html
摘要:對于每個前端程序員來講都有一個終極理想,那就是搞懂引擎是如何工作的。性能經過了兩次飛躍第次飛躍是年發布,第次則是年的。從去年底開始連載源碼分析,記錄一下自己學習源碼的點點滴滴。月星期六晚點和大家一起聊聊引擎前端程序員應該懂點知識講堂。 對于每個前端程序員來講都有一個終極理想,那就是搞懂 javascript 引擎是如何工作的。 從我的網絡 ID(justjavac)可以看出來,當我開始...
摘要:函數調用會在內存形成一個調用記錄,又稱調用幀,保存調用位置和內部變量等信息。等到運行結束,將結果返回到,的調用幀才會消失。方法用于將一組值,轉換為數組。,和遍歷數組方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的方法類似。 ES6 簡介 ES6, 全稱 ECMAScript 6.0 ,是 JavaScript 的下一個版本標準,2015.06 發版。 let 和 const...
摘要:指數運算符是可以進行求冪運算的求冪運算符是兩個星號求冪運算符的優先級高于其它二元運算符,但是一元運算符的優先級高于求冪運算符求冪運算符左側的一元運算符只能是或者是語法錯誤可以這樣包裹也可以這樣包裹在中可通過方法來檢查給定字符串中是 指數運算符 Math.pow是可以進行求冪運算的求冪運算符是兩個星號 let result = 5 ** 2 console.log(result) //2...
摘要:指數運算符是可以進行求冪運算的求冪運算符是兩個星號求冪運算符的優先級高于其它二元運算符,但是一元運算符的優先級高于求冪運算符求冪運算符左側的一元運算符只能是或者是語法錯誤可以這樣包裹也可以這樣包裹在中可通過方法來檢查給定字符串中是 指數運算符 Math.pow是可以進行求冪運算的求冪運算符是兩個星號 let result = 5 ** 2 console.log(result) //2...
閱讀 2465·2021-09-29 09:34
閱讀 3301·2021-09-23 11:21
閱讀 2494·2021-09-06 15:00
閱讀 1123·2019-08-30 15:44
閱讀 2024·2019-08-29 17:23
閱讀 2996·2019-08-29 16:44
閱讀 3052·2019-08-29 13:13
閱讀 1932·2019-08-28 18:12