Usando OR bit a bit para somar um número

192

Um colega meu encontrou um método para calcular números flutuantes usando um bit a bit ou:

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

Estávamos conversando sobre isso e imaginando algumas coisas.

  • Como funciona? Nossa teoria era que o uso de um operador desse tipo lança o número para um número inteiro, removendo a parte fracionária
  • Tem alguma vantagem sobre fazer Math.floor ? Talvez seja um pouco mais rápido? (trocadilho não pretendido)
  • Tem alguma desvantagem? Talvez não funcione em alguns casos? A clareza é óbvia, já que tínhamos que descobrir, e bem, estou escrevendo essa pergunta.

Obrigado.

Alex Turpin
fonte
6
Desvantagem: ele funciona apenas até 2 ^ 31−1, que é de cerca de 2 bilhões (10 ^ 9). O valor do número máximo é de cerca de 10 ^ 308 btw.
Šime Vidas
12
Exemplo: 3000000000.1 | 0avalia para -1294967296. Portanto, esse método não pode ser aplicado para cálculos de dinheiro (especialmente nos casos em que você multiplica por 100 para evitar números decimais).
Šime Vidas
13
@ ŠimeVidas flutua não deve ser usado nos cálculos o dinheiro também
George Reith
20
Não é piso, está truncando (arredondando para 0).
Bartłomiej Zalewski
3
@ seqüência tente digitar 0.1 + 0.2 == 0.3em um console JavaScript. Se o seu idioma suportar, você deve usar um tipo decimal. Caso contrário, armazene centavos.
Alex Turpin #

Respostas:

160

Como funciona? Nossa teoria era que o uso de um operador desse tipo lança o número para um número inteiro, removendo a parte fracionária

Todas as operações bit a bit, exceto shift direito não assinado >>>, funcionam em números inteiros de 32 bits assinados. Portanto, o uso de operações bit a bit converterá um float em um número inteiro.

Tem alguma vantagem sobre o Math.floor? Talvez seja um pouco mais rápido? (trocadilho não pretendido)

http://jsperf.com/or-vs-floor/2 parece um pouco mais rápido

Tem alguma desvantagem? Talvez não funcione em alguns casos? A clareza é óbvia, já que tínhamos que descobrir, e bem, estou escrevendo essa pergunta.

  • Não passará jsLint.
  • Somente números inteiros assinados de 32 bits
  • Estranho comportamento comparativo:, Math.floor(NaN) === NaNenquanto(NaN | 0) === 0
Joe
fonte
9
@harold de fato, porque na verdade não é redondo, apenas trunca.
Alex Turpin
5
Outra possível desvantagem é que Math.floor(NaN) === NaN, enquanto (NaN | 0) === 0. Essa diferença pode ser importante em alguns aplicativos.
Ted Hopp
4
Seu jsperf está produzindo informações de desempenho para loops vazios no chrome devido ao movimento invariante do código. Um teste ligeiramente melhor perf seria: jsperf.com/floor-performance/2
Sam Giles
4
Esta é uma parte padrão de asm.js(onde eu aprendi sobre isso). É mais rápido, se não por outro motivo, porque não está chamando uma função no Mathobjeto, uma função que pode ser substituída a qualquer momento como em Math.floor = function(...).
gman
3
(value | 0) === valuepode ser usado para verificar se um valor é de fato um número inteiro e apenas um número inteiro (como no código-fonte Elm @ dwayne-crooks linked). E foo = foo | 0pode ser usado para coagir qualquer valor a um número inteiro (onde números de 32 bits são truncados e todos os não números se tornam 0).
David Michael Gregg
36

Isso é truncamento em oposição ao revestimento. A resposta de Howard é meio correta; Mas eu acrescentaria que Math.floorfaz exatamente o que é suposto com relação a números negativos. Matematicamente, é isso que é um piso.

No caso descrito acima, o programador estava mais interessado em truncar ou cortar o decimal completamente. Embora a sintaxe que eles usaram oculte o fato de que eles estão convertendo o float em um int.

Chad La Guardia
fonte
7
Esta é a resposta correta, aceita não é. Acrescente a isso que Math.floor(8589934591.1)produz o resultado esperado, 8589934591.1 | 0 NÃO .
Salman A
21

No ECMAScript 6, o equivalente a |0é Math.trunc , devo dizer:

Retorna a parte integral de um número removendo quaisquer dígitos fracionários. Apenas trunca o ponto e os dígitos por trás dele, não importa se o argumento é um número positivo ou 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
fonte
6
Excepto o facto Math.trunc()do trabalho com o número maior ou igual a 2 ^ 31 e | 0não
Nolyurn
10

Seu primeiro ponto está correto. O número é convertido em um número inteiro e, portanto, qualquer dígito decimal é removido. Observe que isso Math.floorarredonda para o próximo número inteiro no sentido de menos infinito e, portanto, fornece um resultado diferente quando aplicado a números negativos.

Howard
fonte
5

Javascript representa Numbercomo números flutuantes de precisão dupla de 64 bits .

Math.floor trabalha com isso em mente.

As operações bit a bit funcionam em números inteiros assinados de 32 bits . Inteiros com sinal de 32 bits usam o primeiro bit como significante negativo e os outros 31 bits são o número. Por esse motivo, os números mínimo e máximo permitidos para números assinados de 32 bits são -2.147.483.648 e 2147483647 (0x7FFFFFFFF), respectivamente.

Então, quando você está fazendo | 0, você está essencialmente fazendo é& 0xFFFFFFFF . Isso significa que qualquer número representado como 0x80000000 (2147483648) ou superior retornará como um número negativo.

Por exemplo:

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

Além disso. As operações bit a bit não "atendem". Eles truncam , o que é o mesmo que dizer, eles se aproximam mais 0. Depois de ir ao redor para números negativos, Math.floorrodadas para baixo , enquanto start bit a bit arredondamento para cima .

Como eu disse antes, Math.flooré mais seguro porque opera com números flutuantes de 64 bits. Bitwise é mais rápido , sim, mas limitado ao escopo assinado de 32 bits.

Para resumir:

  • Bitwise funciona da mesma forma se você trabalhar 0 to 2147483647.
  • Bitwise é um número desativado se você trabalha -2147483647 to 0.
  • Bitwise é completamente diferente para números menores que -2147483648e maiores que 2147483647.

Se você realmente deseja ajustar o desempenho e usar os dois:

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

Apenas para adicionar Math.trunctrabalhos como operações bit a bit. Então você pode fazer isso:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}
Pavio curto
fonte
5
  • As especificações dizem que é convertido em um número inteiro:

    Seja lnum o ToInt32 (lval).

  • Desempenho: isso já foi testado no jsperf .

nota: link morto para especificação removida

pimvdb
fonte