Usando bit a bit 0 para marcar un número

192

Un colega mío se topó con un método para hacer flotar los números del piso usando un bit a bit o:

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

Estábamos hablando de eso y preguntándonos algunas cosas.

  • ¿Como funciona? Nuestra teoría era que el uso de dicho operador convierte el número en un número entero, eliminando así la parte fraccional
  • ¿Tiene alguna ventaja sobre hacerlo Math.floor? Tal vez es un poco más rápido? (juego de palabras no previsto)
  • ¿Tiene alguna desventaja? Tal vez no funciona en algunos casos? La claridad es obvia, ya que tuvimos que resolverlo, y bueno, estoy escribiendo esta pregunta.

Gracias.

Alex Turpin
fuente
66
Desventaja: solo funciona hasta 2 ^ 31−1, que es alrededor de 2 mil millones (10 ^ 9). El valor máximo del número es de alrededor de 10 ^ 308 por cierto.
Šime Vidas
12
Ejemplo: 3000000000.1 | 0evalúa a -1294967296. Por lo tanto, este método no puede aplicarse para los cálculos de dinero (especialmente en los casos en que multiplica por 100 para evitar números decimales).
Šime Vidas
13
@ ŠimeVidas Las carrozas no deberían usarse en cálculos de dinero también
George Reith
20
No es un piso, se está truncando (redondeando hacia 0).
Bartłomiej Zalewski
3
@sequence intente escribir 0.1 + 0.2 == 0.3en una consola de JavaScript. Si su idioma lo admite, debe usar un tipo decimal. Si no, guarde centavos en su lugar.
Alex Turpin

Respuestas:

160

¿Como funciona? Nuestra teoría era que el uso de dicho operador convierte el número en un número entero, eliminando así la parte fraccional

Todas las operaciones bit a bit, excepto el desplazamiento a la derecha sin signo >>>, funcionan en enteros de 32 bits con signo. Por lo tanto, el uso de operaciones bit a bit convertirá un flotante a un entero.

¿Tiene alguna ventaja sobre hacer Math.floor? Tal vez es un poco más rápido? (juego de palabras no previsto)

http://jsperf.com/or-vs-floor/2 parece un poco más rápido

¿Tiene alguna desventaja? Tal vez no funciona en algunos casos? La claridad es obvia, ya que tuvimos que resolverlo, y bueno, estoy escribiendo esta pregunta.

  • No pasará jsLint.
  • Solo enteros con signo de 32 bits
  • Comportamiento comparativo impar: Math.floor(NaN) === NaNmientras que(NaN | 0) === 0
Joe
fuente
9
@harold de hecho, porque de hecho no se redondea, simplemente se trunca.
Alex Turpin
55
Otra posible desventaja es que Math.floor(NaN) === NaN, mientras (NaN | 0) === 0. Esa diferencia puede ser importante en algunas aplicaciones.
Ted Hopp
44
Su jsperf está proporcionando información de rendimiento para bucles vacíos en Chrome debido al movimiento de código invariable del bucle. Una prueba de rendimiento ligeramente mejor sería: jsperf.com/floor-performance/2
Sam Giles
44
Esta es una parte estándar de asm.js(donde aprendí por primera vez). Es más rápido si no por otra razón porque no está llamando a una función en el Mathobjeto, una función que en cualquier momento podría reemplazarse como en Math.floor = function(...).
gman
3
(value | 0) === valuepodría usarse para verificar que un valor es de hecho un número entero y solo un número entero (como en el código fuente de Elm @ dwayne-crooks vinculado). Y foo = foo | 0podría usarse para forzar cualquier valor a un entero (donde los números de 32 bits se truncan y todos los no números se convierten en 0).
David Michael Gregg
36

Esto es truncamiento en oposición al piso. La respuesta de Howard es correcta; Pero agregaría que Math.floorhace exactamente lo que se supone que debe hacer con respecto a los números negativos. Matemáticamente, eso es un piso.

En el caso que describió anteriormente, el programador estaba más interesado en truncar o cortar el decimal por completo. Aunque, la sintaxis que usaron oscurece el hecho de que están convirtiendo el flotante en un int.

Chad La Guardia
fuente
77
Esta es la respuesta correcta, la aceptada no lo es. Agregue a eso que Math.floor(8589934591.1)produce el resultado esperado, 8589934591.1 | 0 NO .
Salman A
21

En ECMAScript 6, el equivalente de |0es Math.trunc , algo así debería decir:

Devuelve la parte integral de un número eliminando los dígitos fraccionarios. Simplemente trunca el punto y los dígitos detrás de él, sin importar si el argumento es un número positivo o negativo.

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
fuente
66
Excepto el hecho de que Math.trunc()funciona con un número mayor o igual a 2 ^ 31 y | 0no funciona
Nolyurn
10

Tu primer punto es correcto. El número se convierte en un entero y, por lo tanto, se eliminan los dígitos decimales. Tenga en cuenta que se Math.floorredondea al siguiente número entero hacia menos infinito y, por lo tanto, da un resultado diferente cuando se aplica a números negativos.

Howard
fuente
5

Javascript representa Numbercomo números flotantes de doble precisión de 64 bits .

Math.floor trabaja con esto en mente.

Las operaciones bit a bit funcionan en enteros con signo de 32 bits . Los enteros con signo de 32 bits usan el primer bit como significante negativo y los otros 31 bits son el número. Debido a esto, los números mínimos y máximos permitidos de 32 bits con signo son -2,147,483,648 y 2147483647 (0x7FFFFFFFF), respectivamente.

Entonces, cuando lo estás haciendo | 0, esencialmente lo estás haciendo & 0xFFFFFFFF. Esto significa que cualquier número que se represente como 0x80000000 (2147483648) o mayor regresará como un número negativo.

Por ejemplo:

 // 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

También. Las operaciones bit a bit no se "plantan". Se truncan , lo que es lo mismo que decir, redondean más cerca 0. Una vez que pasan en torno a los números negativos, Math.floorrondas abajo , mientras que a nivel de bits comienzan redondeo hacia arriba .

Como dije antes, Math.floores más seguro porque funciona con números flotantes de 64 bits. Bitwise es más rápido , sí, pero está limitado al alcance firmado de 32 bits.

Para resumir:

  • Bitwise funciona igual si trabajas desde 0 to 2147483647.
  • Bitwise tiene 1 número de descuento si trabaja desde -2147483647 to 0.
  • Bitwise es completamente diferente para números menores -2147483648y mayores que 2147483647.

Si realmente quieres modificar el rendimiento y usar ambos:

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

Solo para agregar Math.trunctrabajos como operaciones bit a bit. Entonces puedes hacer esto:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}
Mecha corta
fuente
5
  • Las especificaciones dicen que se convierte en un entero:

    Deje que lnum sea ToInt32 (lval).

  • Rendimiento: esto se ha probado en jsperf antes.

nota: enlace muerto a la especificación eliminada

pimvdb
fuente