Chrome中的Math.log2精度发生了变化

Math.log2 precision has changed in Chrome

本文关键字:发生了 变化 精度 log2 中的 Math Chrome      更新时间:2023-09-26

我编写了一个JavaScript程序,它根据元素的数量计算二叉树的深度。我的程序几个月来一直运行良好,但最近我发现在Chrome浏览器和Firefox浏览器中查看网页时有所不同。

特别是在Firefox上:

Math.log2(8) = 3

但现在在Chrome中:

Math.log2(8) = 2.9999999999999996

我的JavaScript程序最初是根据元素的数量来查找二叉树的深度的,如下所示:

var tree_depth = Math.floor(Math.log2(n_elements)) + 1;

我对这个公式做了一个简单的修改,这样它在Chrome上仍然可以正常工作:

var epsilon = 1.e-5;
var tree_depth = Math.floor(Math.log2(n_elements) + epsilon) + 1;

我有两个问题:

  1. 有人注意到最近Chrome中Math.log2的精度发生了变化吗?

  2. 有没有比我上面添加epsilon更优雅的修改?

注意:Math.log2自实施以来实际上没有发生任何更改在V8中。也许你记错了,或者你包含了一个垫片碰巧在Chrome之前得到了这些特殊情况的正确结果包括它自己的CCD_ 3的实现。

此外,您似乎应该使用Math.ceil(x),而不是Math.floor(x) + 1

我该如何解决这个问题

为了避免依赖Math.logMath.log2在JavaScript的不同实现中是准确的(所使用的算法是由实现定义的),如果二进制树中的元素少于232,则可以使用逐位运算符。这显然不是最快的方法(这只是O(n)),但这是一个相对简单的例子:

function log2floor(x) {
  // match the behaviour of Math.floor(Math.log2(x)), change it if you like
  if (x === 0) return -Infinity;
  for (var i = 0; i < 32; ++i) {
    if (x >>> i === 1) return i;
  }
}
console.log(log2floor(36) + 1); // 6

Math.log2目前是如何在不同的浏览器中实现的

Chrome中的当前实现是不准确的,因为它们依赖于Math.log(x)的值乘以Math.LOG2E,这使得它容易受到舍入误差的影响(来源):

// ES6 draft 09-27-13, section 20.2.2.22.
function MathLog2(x) {
  return MathLog(x) * 1.442695040888963407;  // log2(x) = log(x)/log(2).
}

如果您运行的是Firefox,它要么使用本机log2功能(如果存在),要么使用与Chrome类似的实现(源代码)。

唯一的区别是,它们不是相乘,而是除以log(2)

#if !HAVE_LOG2
double log2(double x)
{
    return log(x) / M_LN2;
}
#endif

乘法或除法:有多大区别

为了测试除以Math.LN2和乘以Math.LOG2E之间的差异,我们可以使用以下测试:

function log2d(x) { return Math.log(x) / Math.LN2; }
function log2m(x) { return Math.log(x) * Math.LOG2E; }
// 2^1024 rounds to Infinity
for (var i = 0; i < 1024; ++i) {
  var resultD = log2d(Math.pow(2, i));
  var resultM = log2m(Math.pow(2, i));
  if (resultD !== i) console.log('log2d: expected ' + i + ', actual ' + resultD);
  if (resultM !== i) console.log('log2m: expected ' + i + ', actual ' + resultM);
}

请注意,无论使用哪种函数,对于某些值1,它们仍然有浮点错误。恰好log(2)的浮点表示小于实际值,导致值高于实际值(而log2(e)较低)。这意味着对于这些特殊情况,使用log(2)将向下取整到正确的值。

1:log(pow(2, 29)) / log(2) === 29.000000000000004

您也许可以这样做,而不是

// Math.log2(n_elements) to 10 decimal places
var tree_depth = Math.floor(Math.round(Math.log2(n_elements) * 10000000000) / 10000000000);