2012-12-09 18 views
23

Rozumiem, że >>> rozwiązuje problem przepełnienia: podczas dodawania dwóch dużych pozytywnych długich można uzyskać liczbę ujemną. Czy ktoś może wyjaśnić, w jaki sposób to przesunięcie bitowe magicznie rozwiązuje problem przepełnienia? A jak to jest inne niż >>?Dlaczego w Javie (wysoki + niski)/2 jest zły, ale (wysoki i niski) >>> 1 nie jest?


My podejrzane: Myślę, że ma to związek z faktem, że Java wykorzystuje dwie komplementy więc przelewowy jest właściwa liczba gdybyśmy mieli więcej przestrzeni, ale dlatego, że nie staje się ujemny. Więc kiedy przesuniesz się i wiosłujesz z zerem, magicznie zostaje ustalony dzięki dwóm komplementom. Ale mogę się mylić i ktoś z odrobiną mózgu musi to potwierdzić. :)

+1

Zakładam, że masz na myśli [błąd wyszukiwania binarnego] (http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html) ? – Mehrdad

+1

Skąd wiadomo, że rozwiązuje problem z przepełnieniem? Ponadto, jaki problem z przelewem? –

+1

Joshua Bloch powiedział mi: http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html – JohnPristine

Odpowiedz

46

W skrócie, (high + low) >>> 1 to trik, który wykorzystuje nieużywane znak-bit do wykonania poprawnego średnią liczb nieujemnych.


Przy założeniu, że high i low są zarówno nieujemna, wiemy na pewno, że górny najbardziej bit (znak-bit) wynosi zero.

Tak więc oba high i low są w rzeczywistości 31-bitowymi liczbami całkowitymi.

high = 0100 0000 0000 0000 0000 0000 0000 0000 = 1073741824 
low = 0100 0000 0000 0000 0000 0000 0000 0000 = 1073741824 

Po dodaniu ich razem mogą "rozlać się" na górny bit.

high + low =  1000 0000 0000 0000 0000 0000 0000 0000 
      = 2147483648 as unsigned 32-bit integer 
      = -2147483648 as signed 32-bit integer 

(high + low)/2 = 1100 0000 0000 0000 0000 0000 0000 0000 = -1073741824 
(high + low) >>> 1 = 0100 0000 0000 0000 0000 0000 0000 0000 = 1073741824 
  • W 32-bitowej liczby całkowitej, to przepełnienia i przerzuca ujemny. Dlatego (high + low)/2 jest nieprawidłowy, ponieważ high + low może być ujemny.

  • Jako 32-bitowe liczby całkowite bez znaku suma jest prawidłowa. Wszystko, co potrzebne jest, aby podzielić przez 2.

Oczywiście Java nie obsługuje liczb całkowitych, więc najlepsze, co musimy podzielić przez 2 (jako liczba całkowita bez znaku) jest logiczną prawy shift >>> .

W językach z liczb całkowitych bez znaku (takich jak C i C++), staje się trudniejsze, ponieważ dane wejściowe mogą być pełne 32-bitowych liczb całkowitych. Jednym z rozwiązań jest: low + ((high - low)/2)


Wreszcie wyliczyć różnice między >>>, >> i /:

  • >>> logiczne jest tuż-shift. Wypełnia górne bity wartością zero.
  • >> to arytmetyczne przesunięcie w prawo. Wypełnia górną część kopiami oryginalnego bitu górnego.
  • / to dział.

matematycznego:

  • x >>> 1 traktuje x jako liczba całkowita bez znaku i dzieli się przez dwa. Zaokrągla.
  • x >> 1 traktuje x jako liczbę całkowitą ze znakiem i dzieli ją na dwie. Rusza w kierunku ujemnej nieskończoności.
  • traktuje x jako liczbę całkowitą ze znakiem i dzieli ją na dwie. Zaokrągla w kierunku zera.
+0

Nie wiesz, kiedy chcesz użyć >>> i kiedy chcesz użyć >>. Myślę, że w tym przypadku wybraliśmy >>>, aby naprawić problem z nadmiarem (znak bąblowaniem). Jaka jest reguła wyboru pomiędzy >> i >>>? – JohnPristine

+10

'>>>' jest logiczne przesunięcie w prawo. Wypełnia górne bity wartością zero. '>>' jest arytmetyczną prawą zmianą. Wypełnia górne bity kopiami oryginalnego odgałęzienia. Matematycznie, '>>> 1' traktuje liczbę jako niepodpisaną i dzieli przez dwa zaokrąglenia w dół. '>> 1' traktuje liczbę jako podpisaną i zaokrągla w dół w kierunku ujemnej nieskończoności. '/ 2' traktuje liczbę jako podpisaną i zaokrągla w kierunku zera. – Mysticial

10

To zero - wypełnia najwyższe bity zamiast wypełniać je znakami.

int a = 0x40000000; 
(a + a)/2 == 0xC0000000; 
(a + a) >>> 1 == 0x40000000; 
+0

Zobacz moją odpowiedź na pytanie ... – JohnPristine

+1

@JohnPristine: Well , procesor uzupełniający 2-krotnie wykonuje dodawanie w dokładnie taki sam sposób dla podpisanych i bez znaku liczb całkowitych ... jedyna różnica występuje w '/ 2'. Do tego momentu wszystko jest poprawne. I oczywiście, ponieważ jest wystarczająco dużo bitów do przedstawienia sumy, jest wystarczająco dużo bitów do reprezentowania ilorazu, a ">>> 1" właśnie to robi. Jedynym powodem '/ 2' jest błędne jest to, że próbuje poprawić znak, i oczywiście' >>> 1' wykonuje bitowe przesunięcie w prawo, które jest takie samo jak niepodpisane dzielenie przez 2 ... stąd * musi * działa poprawnie. Nie jestem pewien, czy to odpowiedział na twoje pytanie ... – Mehrdad

+0

Czy moje oświadczenie jest poprawne: "Myślę, że ma to związek z tym, że Java używa dwóch komplementów, więc przepełnienie jest odpowiednią liczbą, jeśli mamy dodatkową przestrzeń, ale ponieważ nie stajemy się negatywni, więc kiedy przesuniesz się i zejmiesz na zero, magicznie zostanie naprawiony "? – JohnPristine