211

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 writting this question.

Thanks.

Alex Turpin
  • 43,483
  • 22
  • 107
  • 143
  • 6
    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. – Šime Vidas Sep 20 '11 at 15:51
  • 12
    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). – Šime Vidas Sep 20 '11 at 16:08
  • 13
    @ŠimeVidas Floats shouldn't be used in money calculations also – George Reith Feb 20 '14 at 10:02
  • 1
    I personally like `~~` for bitwise flooring. `var a = ~~13.6; // a == 13` – John Strickler Nov 19 '14 at 17:48
  • 23
    It is not flooring, it is **truncating** (rounding towards 0). – Bartłomiej Zalewski Dec 09 '14 at 14:40
  • | 0 is faster. Check the benchmark: http://jsben.ch/#/MJvaN – EscapeNetscape Oct 21 '16 at 21:45
  • it's not rounding either - just like @joe's answer said it is casting to an int. The correct way to state this when seeing `| 0` is simply "truncating to int" IMHO – Eat at Joes Nov 14 '16 at 17:12
  • The better 'flooring' could be: `parseInt(""+13.6)`, but it converts float to int. – Thevs Jan 28 '19 at 11:41
  • @GeorgeReith "Floats shouldn't be used in money calculations also". Why not? – sequence Sep 25 '19 at 22:25
  • 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. – Alex Turpin Sep 28 '19 at 02:11
  • 1
    @GeorgeReith unfortunately, with JS, *every* number is a floating point number. However, there do exist JS libraries that allow arbitrary precision numbers by keeping the internal array’s numbers less than `Number.MAX_SAFE_INTEGER` which is `2^53 - 1`. So, the library could, for example, keep it’s internal array’s values under `2^32`. In fact, that’s how JS crypto libraries work. – Cole Johnson May 20 '20 at 15:44
  • 1
    @ColeJohnson indeed, what I meant was only use whole numbers if you only have access to floats. Or as AlexTurpin put it "store cents". – George Reith May 22 '20 at 07:01
  • 1
    Now that `BigInt` is in the language you should use that where possible (outside of cryptography). – George Reith May 22 '20 at 07:08

6 Answers6

173

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
Joe
  • 73,764
  • 18
  • 123
  • 142
  • 1
    wow that's a HUGE performance difference, doesn't it round the wrong way around for negative numbers though? – harold Sep 22 '11 at 12:03
  • 9
    @harold indeed, because it does not in fact round, merely truncates. – Alex Turpin Apr 10 '12 at 18:46
  • 5
    Another possible disadvantage is that `Math.floor(NaN) === NaN`, while `(NaN | 0) === 0`. That difference might be important in some applications. – Ted Hopp Jan 02 '13 at 01:56
  • 4
    Your jsperf is yielding performance information for empty loops on chrome due to loop invariant code motion. A slightly better perf test would be: http://jsperf.com/floor-performance/2 – Sam Giles May 08 '13 at 12:04
  • 1
    Elm is using it to decode integers here: https://github.com/elm-lang/core/blob/3.0.0/src/Native/Json.js#L57. – Dwayne Crooks Feb 25 '16 at 16:21
  • Is the conversion specific to JavaScript or common to bitwise operations in most other languages? – dhfromkorea Oct 13 '16 at 07:03
  • 4
    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(...)`. – gman May 08 '17 at 04:03
  • 3
    `(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). – David Michael Gregg Jan 03 '18 at 11:31
  • @gman Also, with some JS interpreters (thinking of V8), there exists two types of `Number`, the floating point (JS spec compliment), and a 32-bit integer. When V8 sees the `| 0`, it changes the type internally to the integer version (for faster calculations). Then, *if* an operation requires a fractional component, it’ll switch its internal representation back to floating point. Very neat. https://v8.dev/blog/elements-kinds – Cole Johnson May 20 '20 at 15:48
  • "Math.floor(NaN) === NaN" is WRONG, it evaluates false. Maybe rewrite that as "Math.floor(NaN) returns NaN". Reason: "NaN === anything" is always false, so "NaN === NaN" is false! NaN is very much a special-case in all of the comparison operators. – user9876 Mar 31 '21 at 12:28
39

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.

Chad La Guardia
  • 4,758
  • 3
  • 22
  • 35
  • 7
    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**. – Salman A Jan 19 '18 at 18:08
23

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
zangw
  • 33,777
  • 15
  • 127
  • 153
  • 7
    Except the fact that `Math.trunc()` work with number higher or equal to 2^31 and `| 0` does not – Nolyurn Sep 06 '17 at 09:46
11

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.

Howard
  • 36,183
  • 6
  • 60
  • 81
8

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) {
      return (n - 1) & 0xFFFFFFFF;
    }
    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);
}
ShortFuse
  • 4,244
  • 1
  • 27
  • 29
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

Community
  • 1
  • 1
pimvdb
  • 141,012
  • 68
  • 291
  • 345