16

Is there any faster alternative to the following expression:

Math.pow(2,Math.floor(Math.log(x)/Math.log(2)))

That is, taking the closest (smaller) integer power of 2 of a double? I have such expression in a inner loop. I suspect it could be much faster, considering one could just take the mantissa from the IEEE 754 representation of the double.

MaiaVictor
  • 51,090
  • 44
  • 144
  • 286
  • Why dont you hardcode the value of log 2?, or is that too variable? – BatScream Nov 17 '14 at 03:47
  • Ah, I can do that, of course. But I'm still taking a log, then dividing, then taking a floor, then taking a power of 2. That is too much already, when the information is all there already on the double itself! If I just could cast... – MaiaVictor Nov 17 '14 at 03:49
  • http://stackoverflow.com/questions/466204/rounding-off-to-nearest-power-of-2 – BatScream Nov 17 '14 at 03:56
  • I've read the whole thread already actually... most of its answers treats integers and mostly depends on C hacks which aren't available on JS... – MaiaVictor Nov 17 '14 at 03:59
  • That expression will not always work for doubles near powers of two. For example, try x=35184372088831.9 (just below 2^45). Your expression returns 2^45, not 2^44. – Rick Regan Nov 17 '14 at 13:46
  • You can access mantissa & exponent of a JavaScript number using ArrayBuffer and typed arrays, see http://stackoverflow.com/a/17156580/1647737 - however it might not be fast enough for your purpose. – le_m Mar 15 '17 at 19:59
  • the fastest is this one: https://stackoverflow.com/a/74916422/236062 – Zibri Dec 26 '22 at 00:22

8 Answers8

21

Making use of ES6's Math.clz32(n) to count leading zeros of a 32-bit integer:

// Compute nearest lower power of 2 for n in [1, 2**31-1]:
function nearestPowerOf2(n) {
  return 1 << 31 - Math.clz32(n);
}

// Examples:
console.log(nearestPowerOf2(9));  // 8
console.log(nearestPowerOf2(33)); // 32
le_m
  • 19,302
  • 9
  • 64
  • 74
  • Should be very fast in theory, but I didn't test yet. – MaiaVictor Mar 15 '17 at 23:28
  • But beware the polyfill at [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32) . It says clz32(8) is 29 when it should be 28, so if used in this nearestPowerOf2 implementation, you'll get nearestPowerOf2(8) = 4. – Don Hatch Apr 08 '17 at 01:54
  • 2
    the fastest is this one: https://stackoverflow.com/a/74916422/236062 8 times faster on Pc 6 times faster on my cellphone :D – Zibri Dec 26 '22 at 00:23
7

Here's another alternative, with benchmarks. While both seems to be comparable, I like being able to floor or ceil.

function blpo2(x) {
  x = x | (x >> 1);
  x = x | (x >> 2);
  x = x | (x >> 4);
  x = x | (x >> 8);
  x = x | (x >> 16);
  x = x | (x >> 32);
  return x - (x >> 1);
}

function pow2floor(v) {
  var p = 1;
  while (v >>= 1) {
    p <<= 1;
  }
  return p;
}

function pow2ceil(v) {
  var p = 2;
  while (v >>= 1) {
    p <<= 1;
  }
  return p;
}

function MATHpow2(v) {
  return Math.pow(2, Math.floor(Math.log(v) / Math.log(2)))
}


function nearestPowerOf2(n) {
   return 1 << 31 - Math.clz32(n);
}

 function runner(fn, val) {
  var res;
  var st = new Date().getTime()
  for (var i = 0; i < 100000000; i++) {
    fn(val);
  }
  return (new Date().getTime() - st)
}

var source = 300000;

var a = runner(pow2floor, source);
console.log("\n--- pow2floor ---");
console.log(" result: " + pow2floor(source));
console.log(" time: " + a + " ms");

var b = runner(MATHpow2, source);
console.log("\n--- MATHpow2 ---");
console.log(" result: " + MATHpow2(source));
console.log(" time: " + b + " ms");

var b = runner(nearestPowerOf2, source);
console.log("\n--- nearestPowerOf2 ---");
console.log(" result: " + nearestPowerOf2(source));
console.log(" time: " + b + " ms");

var b = runner(blpo2, source);
console.log("\n--- blpo2 ---");
console.log(" result: " + blpo2(source));
console.log(" time: " + b + " ms");

// pow2floor: 1631 ms
// MATHpow2: 13942 ms
// nearestPowerOf2: 937 ms
// blpo2 : 919 ms   **WINNER**
Zibri
  • 9,096
  • 3
  • 52
  • 44
bob
  • 7,539
  • 2
  • 46
  • 42
2

Here is also a branchless 32 bit version which is the fastest (9x) (on cellphones even more!) as of now. It can also be scaled to 64 or 128 bits adding 1 or two lines:

x = x | (x >> 64);
x = x | (x >> 128);

on my computer:

2097152,blpo2: 118 ms **FASTEST**
2097152,nearestPowerOf2: 973 ms
2097152,pow2floor: 2033 ms

on my phone:

2097152,blpo2: 216 ms **FASTEST**
2097152,nearestPowerOf2: 1259 ms
2097152,pow2floor: 2904 ms

function blpo2(x) {
  x = x | (x >> 1);
  x = x | (x >> 2);
  x = x | (x >> 4);
  x = x | (x >> 8);
  x = x | (x >> 16);
  x = x | (x >> 32);
  return x - (x >> 1);
}

function pow2floor(v) {
  var p = 1;
  while (v >>= 1) {
    p <<= 1;
  }
  return p;
}

function nearestPowerOf2(n) {
  return 1 << 31 - Math.clz32(n);
}

function runner(fn, val) {
  var res;
  var st = new Date().getTime()
  for (var i = 0; i < 100000000; i++) {
    res = fn(val);
  }
  dt = new Date().getTime() - st;
  return res + "," + fn.name + ": " + dt + " ms"
}

var source = 3000000;

console.log(runner(blpo2, source), "**FASTEST**")
console.log(runner(nearestPowerOf2, source))
console.log(runner(pow2floor, source))
Zibri
  • 9,096
  • 3
  • 52
  • 44
  • If JS is converting input to integer within the function, then it may be actually even faster for some "directly bitwise representable" (23 bit) double-precision values of integers. – huseyin tugrul buyukisik Dec 26 '22 at 16:39
  • @huseyintugrulbuyukisik the first ">>" automatically converts it to integer from then on is always an integer. – Zibri Dec 27 '22 at 14:38
0

Unfortunately, you would need an equivalent of the C function frexp. The best I've been able to find is in JSFiddle, and its code uses Math.pow.

There are a couple of alternatives you could benchmark, using real data, along with your current attempt:

  1. Starting at 1.0, multiply repeatedly by 2.0 until it is greater than or equal to the input, then multiply by 0.5 until it is less than or equal to the input. You would need special handling for values at the ends of the double range.
  2. Store an ascending value array of all the exact powers of two in the double range, and do a binary search.

The first one is likely to be fastest if your data is typically close to 1.0. The second one requires up to 11 conditional branches.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
0

Without ES6...

x=Math.floor(Math.random()*500000); //for example
nearestpowerof2=2**(x.toString(2).length-1);
console.log(x,">>>",nearestpowerof2);

In other words: the result is 2 to the power of the length of the binary representation of the number subtracted by 1.

Zibri
  • 9,096
  • 3
  • 52
  • 44
0

And another way (this one is slow but it's fun to code recursive ones):

function calc(n, c) {
  c = c || 0;
  n = n >> 1;
  return (n > 0) ? calc(n, c + 1) : 2 ** c;
}

console.log(calc(345367));
console.log(calc(34536));
console.log(calc(3453));
console.log(calc(345));
console.log(calc(34));
Zibri
  • 9,096
  • 3
  • 52
  • 44
0

And this is another.

function nP2(n) {
  return 1 << Math.log2(n);
}

console.log(nP2(345367));
console.log(nP2(34536));
console.log(nP2(3453));
console.log(nP2(345));
console.log(nP2(34));
Zibri
  • 9,096
  • 3
  • 52
  • 44
0

Oh and I forgot the one-liner:

a=3764537465
console.log(2**~~Math.log2(a))

In other words, here we raise 2 to the power of the rounded logarithm in base 2 of the number. But alas, this is 140 times slower than the winner: blpo2 https://stackoverflow.com/a/74916422/236062

Zibri
  • 9,096
  • 3
  • 52
  • 44