Używanie bitowego OR 0 do wpisywania liczby

193

Mój kolega natknął się na metodę wyprowadzania liczb zmiennoprzecinkowych za pomocą bitów lub:

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

Rozmawialiśmy o tym i zastanawialiśmy się nad kilkoma rzeczami.

  • Jak to działa? Nasza teoria była taka, że ​​użycie takiego operatora rzutuje liczbę na liczbę całkowitą, usuwając w ten sposób część ułamkową
  • Czy ma to jakieś zalety w stosunku do robienia Math.floor? Może to trochę szybciej? (gra słów nie przeznaczona)
  • Czy ma to jakieś wady? Może w niektórych przypadkach to nie działa? Klarowność jest oczywista, ponieważ musieliśmy to rozgryźć i cóż, piszę to pytanie.

Dzięki.

Alex Turpin
źródło
6
Wada: działa tylko do 2 ^ 31-1, co stanowi około 2 miliardów (10 ^ 9). Maksymalna wartość liczbowa wynosi około 10 ^ 308 btw.
Šime Vidas,
12
Przykład: 3000000000.1 | 0ocenia na -1294967296. Tak więc tej metody nie można zastosować do obliczania pieniędzy (szczególnie w przypadkach, gdy mnożymy przez 100, aby uniknąć liczb dziesiętnych).
Šime Vidas,
13
@ ŠimeVidas Floats nie powinny być również wykorzystywane do obliczania pieniędzy
George Reith
20
To nie jest podłoga, tylko ścina się (zaokrągla w kierunku 0).
Bartłomiej Zalewski,
3
@sequence spróbuj wpisać 0.1 + 0.2 == 0.3w konsoli JavaScript. Jeśli Twój język to obsługuje, powinieneś użyć typu dziesiętnego. Jeśli nie, zamiast tego przechowuj centy.
Alex Turpin

Odpowiedzi:

161

Jak to działa? Nasza teoria była taka, że ​​użycie takiego operatora rzutuje liczbę na liczbę całkowitą, usuwając w ten sposób część ułamkową

Wszystkie operacje bitowe oprócz prawego przesunięcia bez znaku >>>, działają na 32-bitowych liczbach całkowitych ze znakiem. Zatem użycie operacji bitowych przekształci liczbę zmiennoprzecinkową w liczbę całkowitą.

Czy ma to jakieś zalety w stosunku do robienia Math.floor? Może to trochę szybciej? (gra słów nie przeznaczona)

http://jsperf.com/or-vs-floor/2 wydaje się nieco szybszy

Czy ma to jakieś wady? Może w niektórych przypadkach to nie działa? Klarowność jest oczywista, ponieważ musieliśmy to rozgryźć i cóż, piszę to pytanie.

  • Nie przejdzie jsLint.
  • Tylko 32-bitowe liczby całkowite ze znakiem
  • Dziwne zachowanie porównawcze:, Math.floor(NaN) === NaNpodczas gdy(NaN | 0) === 0
Joe
źródło
9
@ Charold rzeczywiście, ponieważ w rzeczywistości nie zaokrągla, a jedynie obcina.
Alex Turpin
5
Inną możliwą wadą jest to Math.floor(NaN) === NaN, że (NaN | 0) === 0. Ta różnica może być ważna w niektórych aplikacjach.
Ted Hopp
4
Twój jsperf podaje informacje o wydajności dla pustych pętli w chrome z powodu niezmiennego ruchu kodu w pętli. Nieco lepszy test perf byłby: jsperf.com/floor-performance/2
Sam Giles
4
Jest to standardowa część asm.js(gdzie po raz pierwszy się o tym dowiedziałam). Jest szybszy, jeśli bez żadnego innego powodu, ponieważ nie wywołuje funkcji na Mathobiekcie, funkcji, którą można w dowolnym momencie zastąpić jak w Math.floor = function(...).
gman
3
(value | 0) === valuemoże być użyty do sprawdzenia, czy wartość jest w rzeczywistości liczbą całkowitą i tylko liczbą całkowitą (jak w kodzie źródłowym Elm @ połączonymi oszustami). I foo = foo | 0może być użyty do wymuszenia dowolnej wartości na liczbę całkowitą (gdzie liczby 32-bitowe są obcinane, a wszystkie nie-liczby stają się 0).
David Michael Gregg
36

Jest to obcięcie w przeciwieństwie do podłogi. Odpowiedź Howarda jest w pewnym sensie poprawna; Dodałbym jednak, że Math.floorrobi dokładnie to, co powinien, w odniesieniu do liczb ujemnych. Matematycznie taka jest podłoga.

W przypadku opisanym powyżej programista był bardziej zainteresowany obcięciem lub całkowitym odcięciem dziesiętnego. Chociaż zastosowana przez nich składnia przesłania fakt, że przekształcają zmiennoprzecinkowe na int.

Chad La Guardia
źródło
7
To jest prawidłowa odpowiedź, jedna nie jest zaakceptowana. Dodaj do niego, który Math.floor(8589934591.1)daje oczekiwany wynik, 8589934591.1 | 0 NIE .
Salman A
21

W ECMAScript 6 odpowiednikiem |0jest Math.trunc , co powinienem powiedzieć:

Zwraca integralną część liczby poprzez usunięcie wszelkich cyfr ułamkowych. Po prostu obcina kropkę i cyfry za nią, bez względu na to, czy argument jest liczbą dodatnią czy ujemną.

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
źródło
6
Z wyjątkiem faktu, że Math.trunc()praca z liczbą wyższą lub równą 2 ^ 31 i | 0nie działa
Nolyurn
10

Twój pierwszy punkt jest poprawny. Liczba jest rzutowana na liczbę całkowitą, a zatem usuwane są wszystkie cyfry dziesiętne. Zauważ, że Math.floorzaokrągla do następnej liczby całkowitej w kierunku minus nieskończoności, a zatem daje inny wynik, gdy zostanie zastosowany do liczb ujemnych.

Howard
źródło
5

JavaScript reprezentuje Numberjako zmiennoprzecinkowe liczby 64-bitowe Double Precision .

Math.floor działa z tą myślą.

Operacje bitowe działają w 32-bitowych podpisane liczb całkowitych. 32-bitowe liczby całkowite ze znakiem używają pierwszego bitu jako ujemnego znacznika, a pozostałe 31 bitów to liczba. Z tego powodu, minimalna i maksymalna liczba dozwolonych 32-bitowych liczb ze znakami wynosi odpowiednio -2 147 483 648 i 2147483647 (0x7FFFFFFFF).

Więc kiedy to robisz | 0, zasadniczo to robisz & 0xFFFFFFFF. Oznacza to, że każda liczba reprezentowana jako 0x80000000 (2147483648) lub wyższa zwróci liczbę ujemną.

Na przykład:

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

Również. Operacje bitowe nie „piętrzą”. Oni obciąć , który jest taki sam jak powiedzenie, że zaokrąglanie najbliżej 0. Po przejściu do liczb ujemnych Math.floorzaokrągla się w dół, a bitowe zaczyna zaokrąglać w górę .

Jak powiedziałem wcześniej, Math.floorjest bezpieczniejszy, ponieważ działa z liczbami zmiennoprzecinkowymi 64-bitowymi. Bitowe jest szybsze , tak, ale ograniczone do 32-bitowego podpisanego zakresu.

Podsumowując:

  • Bitowe działa tak samo, jeśli pracujesz z 0 to 2147483647.
  • Bitowa to 1 liczba wyłączona, jeśli pracujesz z -2147483647 to 0.
  • Bitowa jest zupełnie inna dla liczb mniejszych -2147483648i większych niż 2147483647.

Jeśli naprawdę chcesz poprawić wydajność i użyć obu:

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

Tylko dodanie Math.truncdziała jak operacje bitowe. Możesz to zrobić:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}
ShortFuse
źródło
5
  • Specyfikacja mówi, że jest konwertowany na liczbę całkowitą:

    Niech lnum będzie ToInt32 (lval).

  • Wydajność: zostało to wcześniej przetestowane w jsperf .

Uwaga: usunięto martwy link do specyfikacji

pimvdb
źródło