247

A colleague of mine stumbled upon a method to floor float numbers using a bitwise or:

var a = 13.6 | 0; //a == 13 

We were talking about it and wondering a few things.

  • How does it work? Our theory was that using such an operator casts the number to an integer, thus removing the fractional part
  • Does it have any advantages over doing Math.floor? Maybe it's a bit faster? (pun not intended)
  • Does it have any disadvantages? Maybe it doesn't work in some cases? Clarity is an obvious one, since we had to figure it out, and well, I'm writing this question.

Thanks.

13
  • 8
    Disadvantage: it only works up to 2^31−1 which is around 2 billion (10^9). The max Number value is around 10^308 btw. Commented Sep 20, 2011 at 15:51
  • 21
    Example: 3000000000.1 | 0 evaluates to -1294967296. So this method can't be applied for money calculations (especially in cases where you multiply by 100 to avoid decimal numbers). Commented Sep 20, 2011 at 16:08
  • 18
    @ŠimeVidas Floats shouldn't be used in money calculations also Commented Feb 20, 2014 at 10:02
  • 30
    It is not flooring, it is truncating (rounding towards 0). Commented Dec 9, 2014 at 14:40
  • 3
    @sequence try typing 0.1 + 0.2 == 0.3 in a JavaScript console. If your language supports it, you should use a decimal type. If not, store cents instead. Commented Sep 28, 2019 at 2:11

7 Answers 7

202

How does it work? Our theory was that using such an operator casts the number to an integer, thus removing the fractional part

All bitwise operations except unsigned right shift, >>>, work on signed 32-bit integers. So using bitwise operations will convert a float to an integer.

Does it have any advantages over doing Math.floor? Maybe it's a bit faster? (pun not intended)

http://jsperf.com/or-vs-floor/2 seems slightly faster

Does it have any disadvantages? Maybe it doesn't work in some cases? Clarity is an obvious one, since we had to figure it out, and well, I'm writting this question.

  • Will not pass jsLint.
  • 32-bit signed integers only
  • Odd Comparative behavior: Math.floor(NaN) === NaN, while (NaN | 0) === 0
Sign up to request clarification or add additional context in comments.

10 Comments

@harold indeed, because it does not in fact round, merely truncates.
Another possible disadvantage is that Math.floor(NaN) === NaN, while (NaN | 0) === 0. That difference might be important in some applications.
Your jsperf is yielding performance information for empty loops on chrome due to loop invariant code motion. A slightly better perf test would be: jsperf.com/floor-performance/2
This is a standard part of asm.js (where I first learned about it). It's faster if for no other reason because it's not calling a function on the Math object, a function that could at anytime be replaced as in Math.floor = function(...).
(value | 0) === value could be used to check that a value is in fact an integer and only an integer (as in the Elm source code @dwayne-crooks linked). And foo = foo | 0 could be used to coerce any value to an integer (where 32-bit numbers are truncated and all non-numbers become 0).
|
46

This is truncation as opposed to flooring. Howard's answer is sort of correct; But I would add that Math.floor does exactly what it is supposed to with respect to negative numbers. Mathematically, that is what a floor is.

In the case you described above, the programmer was more interested in truncation or chopping the decimal completely off. Although, the syntax they used sort of obscures the fact that they are converting the float to an int.

3 Comments

This is the correct answer, accepted one is not. Add to it that Math.floor(8589934591.1) produces expected result, 8589934591.1 | 0 DOES NOT.
You are correct Chad. When I test Math.floor(-5.5) it will return me -6. So if we use bitwise, it will use bitwise -5.5 >> 0 it will return me the correct answer -5
@SoftwareEngineer there's no "correct answer". If you need -6, then the bitwise will return the wrong answer. Math.trunc would be the right method in place of bitwise
29

In ECMAScript 6, the equivalent of |0 is Math.trunc, kind of I should say:

Returns the integral part of a number by removing any fractional digits. It just truncate the dot and the digits behind it, no matter whether the argument is a positive number or a negative number.

Math.trunc(13.37) // 13 Math.trunc(42.84) // 42 Math.trunc(0.123) // 0 Math.trunc(-0.123) // -0 Math.trunc("-1.123")// -1 Math.trunc(NaN) // NaN Math.trunc("foo") // NaN Math.trunc() // NaN 

1 Comment

Except the fact that Math.trunc() work with number higher or equal to 2^31 and | 0 does not
21

Javascript represents Number as Double Precision 64-bit Floating numbers.

Math.floor works with this in mind.

Bitwise operations work in 32bit signed integers. 32bit signed integers use first bit as negative signifier and the other 31 bits are the number. Because of this, the min and max number allowed 32bit signed numbers are -2,147,483,648 and 2147483647 (0x7FFFFFFFF), respectively.

So when you're doing | 0, you're essentially doing is & 0xFFFFFFFF. This means, any number that is represented as 0x80000000 (2147483648) or greater will return as a negative number.

For example:

 // Safe (2147483647.5918 & 0xFFFFFFFF) === 2147483647 (2147483647 & 0xFFFFFFFF) === 2147483647 (200.59082098 & 0xFFFFFFFF) === 200 (0X7FFFFFFF & 0xFFFFFFFF) === 0X7FFFFFFF // Unsafe (2147483648 & 0xFFFFFFFF) === -2147483648 (-2147483649 & 0xFFFFFFFF) === 2147483647 (0x80000000 & 0xFFFFFFFF) === -2147483648 (3000000000.5 & 0xFFFFFFFF) === -1294967296 

Also. Bitwise operations don't "floor". They truncate, which is the same as saying, they round closest to 0. Once you go around to negative numbers, Math.floor rounds down while bitwise start rounding up.

As I said before, Math.floor is safer because it operates with 64bit floating numbers. Bitwise is faster, yes, but limited to 32bit signed scope.

To summarize:

  • Bitwise works the same if you work from 0 to 2147483647.
  • Bitwise is 1 number off if you work from -2147483647 to 0.
  • Bitwise is completely different for numbers less than -2147483648 and greater than 2147483647.

If you really want to tweak performance and use both:

function floor(n) { if (n >= 0 && n < 0x80000000) { return n & 0xFFFFFFFF; } if (n > -0x80000000 && n < 0) { const bitFloored = n & 0xFFFFFFFF; if (bitFloored === n) return n; return bitFloored - 1; } return Math.floor(n); } 

Just to add Math.trunc works like bitwise operations. So you can do this:

function trunc(n) { if (n > -0x80000000 && n < 0x80000000) { return n & 0xFFFFFFFF; } return Math.trunc(n); } 

Comments

12

Your first point is correct. The number is cast to an integer and thus any decimal digits are removed. Please note, that Math.floor rounds to the next integer towards minus infinity and thus gives a different result when applied to negative numbers.

Comments

5
  • The specs say that it is converted to an integer:

    Let lnum be ToInt32(lval).

  • Performance: this has been tested at jsperf before.

note: dead link to spec removed

Comments

0
var myNegInt = -1 * Math.pow(2, 32); var myFloat = 0.010203040506070809; var my64BitFloat = myNegInt - myFloat; var trunc1 = my64BitFloat | 0; var trunc2 = ~~my64BitFloat; var trunc3 = my64BitFloat ^ 0; var trunc4 = my64BitFloat - my64BitFloat % 1; var trunc5 = parseInt(my64BitFloat); var trunc6 = Math.floor(my64BitFloat); console.info(my64BitFloat); console.info(trunc1); console.info(trunc2); console.info(trunc3); console.info(trunc4); console.info(trunc5); console.info(trunc6); 

IMO: The question "How does it work?", "Does it have any advantages over doing Math.floor?", "Does it have any disadvantages?" pale in comparison to "Is it at all logical to use it for this purpose?"

I think, before you try to get clever with your code, you may want to run these. My advice; just move along, there is nothing to see here. Using bitwise to save a few operations and having that matter to you at all, usually means your code architecture needs work. As far as why it may work sometimes, well a stopped clock is accurate twice a day, that does not make it useful. These operators have their uses, but not in this context.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.