5
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

Mark Elliot
  • 68,728
  • 18
  • 135
  • 157
cube
  • 1,714
  • 3
  • 22
  • 31
  • The pn and sn are simply variables for integers. Could just as well be (Math.round(2/4)*4). Thank you – cube Jul 12 '10 at 23:59
  • Are you sure you mean bit shifting? Bit shifting would double or half the value for each shift you do. If var2 were always a power of 2 then it would work, but if var2 was ever an odd number the logic to handle it would be more overhead than just dividing it. – Andir Jul 13 '10 at 02:20
  • read this several times and still don't understand your question .. please rephrase what you are trying to do – Scott Evernden Jul 13 '10 at 02:33
  • Ok, doesn't a bitwise shift of zero (>>0) always round down? so just trying to find a bitwise shift formula that can round up or down depending on the values of the var's. – cube Jul 13 '10 at 02:45
  • >>0 is a no-op .. what are you trying to do ? – Scott Evernden Jul 13 '10 at 02:57
  • @Scott: Any bitwise operator in JS truncates the number to an integer, so it's not a no-op. @cube: I don't think it's possible without an additional `if`. – casablanca Jul 13 '10 at 03:31
  • Heres some alternative methods to rounding I just stumbled across - Flooring and rounding You may be used to the Math library but when going short, you need every byte you can get. Internally, most bit-wise operators actually do a 32bit cast first. This means that whenever you apply a float to a bit-wise operator, the result will be the operation of the floored argument instead. That's why these are the same for all numbers: Math.floor(x), ~~x, x|0. To round, simply add .5 to the argument first. Full article found here http://qfox.nl/notes/111 – Okeydoke Aug 11 '10 at 05:15

5 Answers5

8

[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;
    }
}
Andrew
  • 12,936
  • 13
  • 52
  • 102
  • 1
    I don't want to check, but perhaps keeping a direct reference to `Math.round`, say `var round = Math.round;` would give a more accurate result. – Thomas Eding Jul 13 '10 at 03:41
  • True. It does shave about 2% off the execution time on Firefox and Chrome. In the latest Safari (which is abou 5x faster), the difference is negligible. I do think that there is a problem with my bit-shifting math though. I'm working to correct that. – Andrew Jul 13 '10 at 03:56
  • 2
    Adding 0.5 does apparently leave the same value as Math.round(). Updating answer. I've tested for only positive integers and I think this will not work with any integer less than 1. – Andrew Jul 13 '10 at 04:08
  • Thank you Andrew, and trinithis as well. Andrew, I have Google'd for days trying to find any kind of write up, blog, or anything, on making a more efficient rounding method. I could find not one single note on the subject. You are brilliant! Thanks again! – cube Jul 13 '10 at 23:13
  • Very happy to help. It was an interesting problem! I also learned something from this. Back in primary school, I learned that decimal numbers that lay exactly between two whole numbers are supposed to round to the nearest even number (i.e. 1.5 -> 2, 2.5 -> 2,3.5 -> 4,etc.). In JS, these numbers always round up (towards positive infinity). That tidbit simplified the logic a lot! – Andrew Jul 14 '10 at 02:21
  • @Andrew - Wow, you learned "banker's rounding" in primary school. I learned the simple round-up method and had never even heard of banker's rounding until after college. – benjismith Jul 15 '10 at 00:34
5

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>
DaveAlger
  • 2,006
  • 22
  • 23
3

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.

ragnarius
  • 5,018
  • 9
  • 42
  • 61
1

You can do (var | 0) - that would truncate the number to an integer, but you'll always get the floor value. If you want to round it, you'll need an additional if statement, but in this case Math.round would be faster anyway.

casablanca
  • 66,266
  • 7
  • 126
  • 145
-2

Unfortunately, bit shifting operations usually only work with integers. Are your variables integers or floats?

benjismith
  • 15,853
  • 8
  • 55
  • 76
  • The pn and sn are simply variables for integers. Could just as well be (Math.round(2/4)*4). Thank you – cube Jul 13 '10 at 00:32