Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript bitshift alternative to math.round

Tags:

javascript

var1=anyInteger
var2=anyInteger

(Math.round(var1/var2)*var2)

What would be the syntax for JavaScripts bitshift alternative for the above?

Using integer not floating

Thank you

like image 230
cube Avatar asked Jul 13 '10 02:07

cube


3 Answers

[UPDATED] The quick answer:

var intResult = ((((var1 / var2) + 0.5) << 1) >> 1) * var2;

It's faster than the Math.round() method provided in the question and provides the exact same values.

Bit-shifting is between 10 and 20% faster from my tests. Below is some updated code that compares the two methods.

The code below has four parts: first, it creates 10,000 sets of two random integers; second, it does the round in the OP's question, stores the value for later comparison and logs the total time of execution; third, it does an equivalent bit-shift, stored the value for later comparison, and logs the execution time; fourth, it compares the Round and Bit-shift values to find any differences. It should report no anomalies.

Note that this should work for all positive, non-zero values. If the code encounters a zero for the denominator, it will raise and error, and I'm pretty sure that negative values will not bit-shift correctly, though I've not tested.

var arr1 = [],
    arr2 = [],
    arrFloorValues = [],
    arrShiftValues = [],
    intFloorTime = 0,
    intShiftTime = 0,
    mathround = Math.round, // @trinithis's excellent suggestion
    i;

// Step one: create random values to compare
for (i = 0; i < 100000; i++) {
    arr1.push(Math.round(Math.random() * 1000) + 1);
    arr2.push(Math.round(Math.random() * 1000) + 1);
}

// Step two: test speed of Math.round()
var intStartTime = new Date().getTime();
for (i = 0; i < arr1.length; i++) {
    arrFloorValues.push(mathround(arr1[i] / arr2[i]) * arr2[i]);
}
console.log("Math.floor(): " + (new Date().getTime() - intStartTime));

// Step three: test speed of bit shift
var intStartTime = new Date().getTime();
for (i = 0; i < arr1.length; i++) {
    arrShiftValues.push( ( ( ( (arr1[i] / arr2[i]) + 0.5) << 1 ) >> 1 ) * arr2[i]);

}
console.log("Shifting: " + (new Date().getTime() - intStartTime));

// Step four: confirm that Math.round() and bit-shift produce same values
intMaxAsserts = 100;
for (i = 0; i < arr1.length; i++) {
    if (arrShiftValues[i] !== arrFloorValues[i]) {
        console.log("failed on",arr1[i],arr2[i],arrFloorValues[i],arrShiftValues[i])
        if (intMaxAsserts-- < 0) break;
    }
}
like image 167
Andrew Avatar answered Oct 20 '22 06:10

Andrew


you should be able to round any number by adding 0.5 then shifting off the decimals...

var anyNum = 3.14;
var rounded = (anyNum + 0.5) | 0;

so the original expression could be solved using this (instead of the slower Math.round)

((var1/var2 + 0.5)|0) * var2

Run the code snippet below to test different values...

function updateAnswer() {
  var A = document.getElementById('a').value;
  var B = document.getElementById('b').value;

  var h = "Math.round(A/B) * B = <b>" + (Math.round(A/B) * B) + "</b>";
  h += "<br/>";
  h += "((A/B + 0.5)|0) * B = <b>" + ((A/B + 0.5) | 0) * B +"</b>";
  
  document.getElementById('ans').innerHTML = h;
}
*{font-family:courier}
  A: <input id="a" value="42" />
  <br/>
  B: <input id="b" value="7" />
  <br/><br/>
  <button onclick="updateAnswer()">go</button>
  <hr/>
  <span id="ans"></span>
like image 42
DaveAlger Avatar answered Oct 20 '22 08:10

DaveAlger


If var2 is a power of two (2^k) you may write

(var1>>k)<<k

but in the general case there is no straightforward solution.

like image 36
ragnarius Avatar answered Oct 20 '22 08:10

ragnarius