Dziwne zachowanie, gdy Java konwertuje wartość int na bajt?


130
int i =132;

byte b =(byte)i; System.out.println(b);

Oszałamiające. Dlaczego jest wyjście -124?

Odpowiedzi:


172

W Javie jest intto 32 bity. A bytewynosi 8 bits.

Większość prymitywne typy w Javie są podpisane, a byte, short, int, i longsą zakodowane w dwóch uzupełnień. ( charTyp jest bez znaku, a pojęcie znaku nie ma zastosowania boolean).

W tym schemacie liczbowym najbardziej znaczący bit określa znak liczby. Jeśli potrzeba więcej bitów, najbardziej znaczący bit („MSB”) jest po prostu kopiowany do nowego MSB.

Więc jeśli masz bajt 255: 11111111 i chcesz przedstawić go jako int(32 bity), po prostu skopiuj 1 z lewej strony 24 razy.

Teraz jednym ze sposobów odczytania liczby uzupełnienia ujemnego do dwóch jest rozpoczęcie od najmniej znaczącego bitu, przesuwanie w lewo, aż znajdziesz pierwszą 1, a następnie odwracanie każdego bitu. Wynikowa liczba jest dodatnią wersją tej liczby

Na przykład: 11111111idzie do 00000001= -1. To właśnie Java wyświetli wartość.

To, co prawdopodobnie chcesz zrobić, to znać wartość bajtu bez znaku.

Możesz to osiągnąć za pomocą maski bitowej, która usuwa wszystko oprócz najmniej znaczących 8 bitów. (0xff)

Więc:

byte signedByte = -1;
int unsignedByte = signedByte & (0xff);

System.out.println("Signed: " + signedByte + " Unsigned: " + unsignedByte);

Wydrukowałoby: "Signed: -1 Unsigned: 255"

Co się tu właściwie dzieje?

Używamy bitowego AND do zamaskowania wszystkich dodatkowych bitów znaku (jedynek po lewej stronie najmniej znaczących 8 bitów). Kiedy int jest konwertowany na bajt, Java odcina skrajne lewe 24 bity

1111111111111111111111111010101
&
0000000000000000000000001111111
=
0000000000000000000000001010101

Ponieważ 32. bit jest teraz bitem znaku zamiast ósmego (i ustawiamy bit znaku na 0, który jest dodatni), oryginalne 8 bitów z bajtu jest odczytywane przez Javę jako wartość dodatnia.


1
dobra robota, najlepsze wyjaśnienie na ten temat, Wayne! Po prostu szukam formalizacji matematycznej, dlaczego w reprezentacji dopełnienia do dwóch bit znaku można skopiować po prawej stronie, aby dodać bity. Łatwo to zrozumieć, myśląc o zasadzie, jak uzyskać ujemną liczbę. to znaczy: rozważ wszystkie bity od prawej do lewej i zapisz je niezmienione, aż zostanie uwzględniona pierwsza 1. Następnie odwróć kolejne bity. Jeśli uznam, że brakującym bitem jest 0, łatwo jest zrozumieć, że wszystkie idą do 1. Ale szukałem bardziej „matematycznego” wyjaśnienia.
AgostinoX

Dzieje się tutaj signedByte & (0xff)tak, że 0xffjest to literał międzygałowy, dlatego signedByte jest promowane do liczby całkowitej przed wykonaniem operacji bitowej.
Kevin Wheeler

To nie jest 0xFF, w twoim przykładzie jest to 0x7E!
JohnyTex,

89

132cyframi ( podstawa 10 ) jest 1000_0100bitami ( podstawa 2 ), a Java przechowuje intw 32 bitach:

0000_0000_0000_0000_0000_0000_1000_0100

Algorytm int-to-byte jest skracany do lewej; Algorytm System.out.printlnjest uzupełnienie do dwóch (uzupełnienie do dwóch jest, jeśli bit od lewej 1, interpretować jako negatywne one's-dopełniacza (inwertowanym bitów) minus jeden.); Tak więc System.out.println(int-to-byte( ))jest:

  • interpretować jako (jeśli-skrajny-lewy-bit-to-1 [ujemny (odwrócony-bit (minus-jeden (] left-truncate ( 0000_0000_0000_0000_0000_0000_1000_0100) [)))])
  • = interpretuj-jako (jeśli-skrajny-lewy-bit-to-1 [ujemny (odwrócony-bit (minus-jeden (] 1000_0100[)))])
  • = interpretuj jako (ujemne (odwrócone bity (minus jeden ( 1000_0100))))
  • = interpretuj-jako (ujemne (odwrócone-bity ( 1000_0011)))
  • = interpretuj jako (negacja ( 0111_1100))
  • = interpretuj jako (wartość ujemna (124))
  • = zinterpretuj jako (-124)
  • = -124 Tada !!!

7
Bardzo ładnie wyjaśnione
ZAJ

1
Więc teraz 132 dziesiętnie to -124 w bajtach. Jak działa odwrotność?
Nilesh Deokar

@NileshDeokar, Odwrotna strona jest autorstwa POLA, ponieważ pasują (; por. JLS 5.1.2 ); wyjście pokrywa się ze znakiem-leftpad ( 0dla pozytywów i 1negatywów).
Pacerier

Co to jest POLA? Konwersja z intna a bytejest konwersją stratną (tj. Utrata informacji). Dlatego nie ma możliwości przekonwertowania go z powrotem do pierwotnej intwartości.
eigenfield

23

bajt w Javie jest podpisany, więc ma zakres od -2 ^ 7 do 2 ^ 7-1 - tj. od -128 do 127. Ponieważ 132 jest powyżej 127, kończy się zawijanie wokół 132-256 = -124. Oznacza to, że zasadniczo 256 (2 ^ 8) jest dodawane lub odejmowane, aż znajdzie się w zakresie.

Aby uzyskać więcej informacji, możesz przeczytać więcej na temat uzupełnień do dwóch .


16

132 znajduje się poza zakresem bajtu, który wynosi od -128 do 127 (Byte.MIN_VALUE do Byte.MAX_VALUE) Zamiast tego górny bit wartości 8-bitowej jest traktowany jako znak ze znakiem, co oznacza, że ​​w tym przypadku jest ujemny. Więc liczba to 132 - 256 = -124.


5

oto bardzo mechaniczna metoda bez rozpraszających teorii:

  1. Zamień liczbę na reprezentację binarną (użyj kalkulatora, ok?)
  2. Skopiuj tylko skrajnie prawe 8 bitów (LSB) i odrzuć resztę.
  3. Z wyniku kroku 2, jeśli skrajny lewy bit ma wartość 0, użyj kalkulatora, aby przekonwertować liczbę na dziesiętną. To jest twoja odpowiedź.
  4. W przeciwnym razie (jeśli skrajny lewy bit to 1) twoja odpowiedź jest przecząca. Pozostaw wszystkie skrajne prawe zera i pierwszy niezerowy bit bez zmian. I odwróć resztę, czyli zamień jedynki na 0, a 0 na 1. Następnie użyj kalkulatora, aby przekonwertować na dziesiętną i dodaj znak minus, aby wskazać, że wartość jest ujemna.

Ta bardziej praktyczna metoda jest zgodna z dużo teoretycznymi odpowiedziami powyżej. Tak więc ci, którzy nadal czytają te książki o Javie, które mówią, że używają modulo, jest to zdecydowanie błędne, ponieważ 4 kroki, które opisałem powyżej, zdecydowanie nie są operacją modulo.


Jakie książki w języku Java mówią o używaniu „modulo”? Nigdy nie widziałem żadnej książki CS, która by to potwierdzała od 46 lat, nie mówiąc już o żadnej książce Java. Co „modulo”? W Javie nie ma operacji modulo. Tylko operator reszty.
Markiz Lorne

grep mocniej. http://iiti.ac.in/people/~tanimad/JavaTheCompleteReference.pdfstrona 59
eigenfield

4

Równanie uzupełnienia do dwóch:

wprowadź opis obrazu tutaj


W Javie, byte(N = 8) i int(N = 32) są reprezentowane przez uzupełnienie do 2s pokazane powyżej.

Z równania 7 jest ujemne dla, byteale dodatnie dla int.

coef:   a7    a6  a5  a4  a3  a2  a1  a0
Binary: 1     0   0   0   0   1   0   0
----------------------------------------------
int:    128 + 0 + 0 + 0 + 0 + 4 + 0 + 0 =  132
byte:  -128 + 0 + 0 + 0 + 0 + 4 + 0 + 0 = -124

2

często w książkach można znaleźć wyjaśnienie rzutowania z int na bajt jako wykonywane przez dzielenie modułu. nie jest to do końca poprawne, jak pokazano poniżej, co faktycznie się dzieje, to 24 najbardziej znaczące bity z wartości binarnej liczby int są odrzucane, pozostawiając zamieszanie, jeśli ustawiony jest pozostały skrajny lewy bit, który oznacza liczbę jako ujemną

public class castingsample{

public static void main(String args[]){

    int i;
    byte y;
    i = 1024;
    for(i = 1024; i > 0; i-- ){

      y = (byte)i;
      System.out.print(i + " mod 128 = " + i%128 + " also ");
      System.out.println(i + " cast to byte " + " = " + y);

    }

}

}

2
Nigdy nie widziałem tego w żadnej książce od 46 lat.
Markiz Lorne

2

Szybki algorytm symulujący sposób działania jest następujący:

public int toByte(int number) {
    int tmp = number & 0xff
    return (tmp & 0x80) == 0 ? tmp : tmp - 256;
}

Jak to działa? Spójrz na odpowiedź daixtr . Implementacja dokładnego algorytmu opisanego w jego odpowiedzi jest następująca:

public static int toByte(int number) {
    int tmp = number & 0xff;
    if ((tmp & 0x80) == 0x80) {
        int bit = 1;
        int mask = 0;
        for(;;) {
            mask |= bit;
            if ((tmp & bit) == 0) {
                bit <<=1;
                continue;
            }
            int left = tmp & (~mask);
            int right = tmp & mask;
            left = ~left;
            left &= (~mask);
            tmp = left | right;
            tmp = -(tmp & 0xff);
            break;
        }
    }
    return tmp;
}

1

Jeśli chcesz zrozumieć to matematycznie, powiedz, jak to działa

więc zasadniczo liczby b / w -128 do 127 będą zapisywane tak samo jak ich wartość dziesiętna, powyżej jej (twoja liczba - 256).

na przykład. 132, odpowiedź będzie 132 - 256 = - 124 tj

256 + twoja odpowiedź w numerze 256 + (-124) to 132

Inny przykład

double a = 295.04;
int b = 300;
byte c = (byte) a;
byte d = (byte) b; System.out.println(c + " " + d);

Wynik wyniesie 39 44

(295–256) (300–256)

UWAGA: nie będzie uwzględniać liczb po przecinku.


0

Koncepcyjnie do Twojej liczby odejmowane są wielokrotne odejmowania 256, aż znajdzie się w zakresie od -128 do +127. Więc w twoim przypadku zaczynasz od 132, a kończysz na -124 w jednym kroku.

Pod względem obliczeniowym odpowiada to wyodrębnieniu 8 najmniej znaczących bitów z pierwotnej liczby. (Zauważ, że najbardziej znaczący bit z tych 8 staje się bitem znaku).

Zauważ, że w innych językach to zachowanie nie jest zdefiniowane (np. C i C ++).


Żeby było jasne, otrzymany wynik jest taki sam jak w przypadku wielokrotnego odejmowania. W praktyce JVM w rzeczywistości nie robi tego w ten sposób. (Byłoby to strasznie nieefektywne!)
Stephen C

W rzeczy samej. Mam nadzieję, że mój drugi akapit opisuje, jak faktycznie to robi maszyna JVM. Ale trochę bawiłem się językiem.
Batszeba,

1
Tak. Zmiana „zasadniczo” na „koncepcyjnie” robi ogromną różnicę!
Stephen C

-1
 N is input number
case 1: 0<=N<=127  answer=N;
case 2: 128<=N<=256 answer=N-256 
case 3: N>256   
        temp1=N/256;
        temp2=N-temp*256;
        if temp2<=127   then answer=temp2;
        else if temp2>=128  then answer=temp2-256;
case 4: negative  number input
        do same procedure.just change the sign of the solution           

Prawidłowa odpowiedź jest uzyskiwana przez maskowanie bitów, a nie przez dzielenie i resztę.
Markiz Lorne
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.