在现在的主流 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<int>(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<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
因而第一个效果更靠近正确的值。
上周(2017年1月16日)这个奇异的行动已作为一个 bug 提交给了 V8 项目,bug 编号 #5848。