I\'ve written a JavaScript program that calculates the depth of a binary tree based on the number of elements. My program has been working fine for months, but recently I\'ve f
Note:
Math.log2
hasn't actually changed since it's been implemented in V8. Maybe you remembered incorrectly or you had included a shim that happened to get the result correct for these special cases before Chrome included its own implementation ofMath.log2
.Also, it seems that you should be using
Math.ceil(x)
rather thanMath.floor(x) + 1
.
To avoid relying on Math.log
or Math.log2
being accurate amongst different implementations of JavaScript (the algorithm used is implementation-defined), you can use bitwise operators if you have less than 232 elements in your binary tree. This obviously isn't the fastest way of doing this (this is only O(n)), but it's a relatively simple example:
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
currently implemented in different browsers?The current implementation in Chrome is inaccurate as they rely on multiplying the value of Math.log(x)
by Math.LOG2E
, making it susceptible to rounding error (source):
// ES6 draft 09-27-13, section 20.2.2.22.
function MathLog2(x) {
return MathLog(x) * 1.442695040888963407; // log2(x) = log(x)/log(2).
}
If you are running Firefox, it either uses the native log2
function (if present), or if not (e.g. on Windows), uses a similar implementation to Chrome (source).
The only difference is that instead of multiplying, they divide by log(2)
instead:
#if !HAVE_LOG2
double log2(double x)
{
return log(x) / M_LN2;
}
#endif
double
js::math_log2_impl(MathCache *cache, double x)
{
return cache->lookup(log2, x, MathCache::Log2);
}
double
js::math_log2_uncached(double x)
{
return log2(x);
}
bool
js::math_log2(JSContext *cx, unsigned argc, Value *vp)
{
return math_function<math_log2_impl>(cx, argc, vp);
}
All the other code is just to cache the results in a table, which Chrome does not do. This does not have any effect on the accuracy of Firefox's Math.log2
function.
To test the difference between dividing by Math.LN2
and multiplying by Math.LOG2E
, we can use the following test:
function log2d(x) { return Math.log(x) / Math.LN2; }
function log2m(x) { return Math.log(x) * Math.LOG2E; }
var pow = Math.pow;
// 2^1024 rounds to Infinity
for (var i = 0; i < 1024; ++i) {
var resultD = log2d(pow(2, i));
var resultM = log2m(pow(2, i));
if (resultD !== i) console.log('log2d: expected ' + i + ', actual ' + resultD);
if (resultM !== i) console.log('log2m: expected ' + i + ', actual ' + resultM);
}
Please note that no matter which function you use, they still have floating point errors for certain values1. It just so happens that the floating point representation of log(2)
is less than the actual value, resulting in a value higher than the actual value (while log2(e)
is lower). This means that using log(2)
will round down to the correct value for these special cases.
1: log(pow(2, 29)) / log(2) === 29.000000000000004
You could perhaps do this instead
// Math.log2(n_elements) to 10 decimal places
var tree_depth = Math.floor(Math.round(Math.log2(n_elements) * 10000000000) / 10000000000);