¿Comportamiento extraño cuando Java convierte int en byte?

130
int i =132;

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

Alucinante. ¿Por qué es la salida -124?

duro
fuente

Respuestas:

172

En Java, un intes de 32 bits. A bytees 8 bits.

Se firman la mayoría de los tipos primitivos de Java, y byte, short, int, y longestán codificados en complemento a dos. (El chartipo no está firmado y el concepto de signo no es aplicable a boolean).

En este esquema de números, el bit más significativo especifica el signo del número. Si se necesitan más bits, el bit más significativo ("MSB") simplemente se copia al nuevo MSB.

Entonces, si tiene un byte 255: 11111111 y desea representarlo como un int(32 bits), simplemente copie el 1 a la izquierda 24 veces.

Ahora, una forma de leer un número de complemento negativo de dos es comenzar con el bit menos significativo, moverse hacia la izquierda hasta encontrar el primer 1, luego invertir cada bit después. El número resultante es la versión positiva de ese número.

Por ejemplo: 11111111va a 00000001= -1. Esto es lo que Java mostrará como el valor.

Lo que probablemente quiera hacer es conocer el valor sin signo del byte.

Puede lograr esto con una máscara de bits que elimina todo menos los 8 bits menos significativos. (0xff)

Entonces:

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

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

Imprimiría: "Signed: -1 Unsigned: 255"

¿Qué está pasando realmente aquí?

Estamos utilizando AND a nivel de bits para enmascarar todos los bits de signos extraños (los 1 a la izquierda de los 8 bits menos significativos). Cuando un int se convierte en un byte, Java corta los 24 bits más a la izquierda

1111111111111111111111111010101
&
0000000000000000000000001111111
=
0000000000000000000000001010101

Dado que el bit 32 ahora es el bit de signo en lugar del bit 8 (y establecemos el bit de signo en 0, que es positivo), Java lee los 8 bits originales del byte como un valor positivo.

Wayne
fuente
1
bien hecho, la mejor explicación sobre este tema, Wayne! Solo estoy buscando la formalización matemática por qué en la representación del complemento a dos el bit de signo se puede copiar a la derecha para agregar bits. Es fácil entenderlo pensando en la regla de cómo obtener el negativo de un número. es decir: considere todos los bits de derecha a izquierda y escríbalos sin cambios hasta el primer 1 comprendido. Luego invierta los bits subsiguientes. Si considero que el bit faltante es 0s, es fácil entender que todos van a 1. Pero estaba buscando una explicación más 'matemática'.
AgostinoX
Lo signedByte & (0xff)que 0xffestá sucediendo aquí es que es un literal entero, por lo queignedByte se promociona a un entero antes de que se realice la operación bit a bit.
Kevin Wheeler
¡Eso no es 0xFF, es 0x7E en tu ejemplo!
JohnyTex
89

132en dígitos ( base 10 ) está 1000_0100en bits ( base 2 ) y Java almacena inten 32 bits:

0000_0000_0000_0000_0000_0000_1000_0100

El algoritmo para int-by-byte es truncado a la izquierda; Algoritmo para System.out.printlnes el complemento a dos (el complemento a dos es si el bit más a la izquierda es 1, interpretar como complemento a uno negativo (bits invertidos) menos uno); Así System.out.println(int-to-byte( ))es:

  • interpretar-como (if-leftmost-bit-is-1 [negativo (invertir-bits (menos-uno (] left-truncate ( 0000_0000_0000_0000_0000_0000_1000_0100) [)))])
  • = interpretar-como (if-leftmost-bit-is-1 [negativo (invertir-bits (menos uno () 1000_0100[)))])
  • = interpretar como (negativo (invertir-bits (menos uno ( 1000_0100))))
  • = interpretar como (negativo (invertir-bits ( 1000_0011)))
  • = interpretar como (negativo ( 0111_1100))
  • = interpretar como (negativo (124))
  • = interpretar como (-124)
  • = -124 Tada !!!
Pacerier
fuente
77
Muy bien explicado
ZAJ
1
Entonces ahora 132 en decimal es -124 en byte. ¿Cómo funciona lo contrario?
Nilesh Deokar
@NileshDeokar, lo contrario es POLA ya que encajan (; cf JLS 5.1.2 ); la salida coincide con el signo-leftpad ( 0para positivo y 1para negativo).
Pacerier
¿Qué es POLA? La conversión de inta a bytees una conversión con pérdida (es decir, se pierde información). Por lo tanto, no hay forma de convertirlo nuevamente a su intvalor original .
truthadjustr
23

el byte en Java está firmado, por lo que tiene un rango de -2 ^ 7 a 2 ^ 7-1, es decir, -128 a 127. Dado que 132 está por encima de 127, terminas ajustando a 132-256 = -124. Es decir, esencialmente 256 (2 ^ 8) se suman o restan hasta que caen dentro del rango.

Para obtener más información, es posible que desee leer sobre el complemento de dos .

bdonlan
fuente
16

132 está fuera del rango de un byte que es -128 a 127 (Byte.MIN_VALUE a Byte.MAX_VALUE) En cambio, el bit superior del valor de 8 bits se trata como el signo que indica que es negativo en este caso. Entonces el número es 132-256 = -124.

Peter Lawrey
fuente
5

Aquí hay un método muy mecánico sin las teorías de distracción:

  1. Convierta el número en representación binaria (use una calculadora ¿ok?)
  2. Solo copie los 8 bits más a la derecha (LSB) y descarte el resto.
  3. Según el resultado del paso 2, si el bit más a la izquierda es 0, use una calculadora para convertir el número a decimal. Esta es tu respuesta.
  4. De lo contrario (si el bit más a la izquierda es 1) su respuesta es negativa. Deje todos los ceros más a la derecha y el primer bit distinto de cero sin cambios. Y revirtió el resto, es decir, reemplazar 1 por 0 y 0 por 1. Luego use una calculadora para convertir a decimal y agregue un signo negativo para indicar que el valor es negativo.

Este método más práctico está de acuerdo con las respuestas teóricas anteriores. Entonces, aquellos que todavía leen esos libros de Java que dicen usar el módulo, esto definitivamente es incorrecto ya que los 4 pasos que describí anteriormente definitivamente no son una operación de módulo.

truthadjustr
fuente
¿Qué dicen los libros de Java para usar 'módulo'? Nunca he visto ningún libro de CS que indique eso en 46 años, y mucho menos cualquier libro de Java. ¿Qué 'módulo'? No hay operación de módulo en Java. Solo un operador restante.
Marqués de Lorne
grep más duro. http://iiti.ac.in/people/~tanimad/JavaTheCompleteReference.pdfpágina 59
truthadjustr
4

Ecuación del complemento a dos:

ingrese la descripción de la imagen aquí


En Java, byte(N = 8) y int(N = 32) están representados por el complemento 2s que se muestra arriba.

De la ecuación, un 7 es negativo para bytepero positivo para 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
bcorso
fuente
2

a menudo en los libros encontrará la explicación de la conversión de int a byte como realizada por división de módulo. esto no es estrictamente correcto, como se muestra a continuación, lo que realmente sucede es que los 24 bits más significativos del valor binario del número int se descartan, dejando confusión si se establece el bit que queda más a la izquierda, que designa el número como negativo

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);

    }

}

}
bolígrafo
fuente
2
Nunca he visto eso en ningún libro en 46 años.
Marqués de Lorne
2

Un algoritmo rápido que simula la forma en que funciona es el siguiente:

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

¿Cómo funciona esto? Mira a la respuesta daixtr . Una implementación del algoritmo exacto descrito en su respuesta es la siguiente:

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;
}
Francisco Neto
fuente
1

Si quieres entender esto matemáticamente, como cómo funciona

así que, básicamente, los números b / w -128 a 127 se escribirán igual que su valor decimal, por encima de eso (su número - 256).

p.ej. 132, la respuesta será 132 - 256 = - 124 es decir

256 + tu respuesta en el número 256 + (-124) es 132

Otro ejemplo

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

la salida será 39 44

(295 - 256) (300 - 256)

NOTA: no considerará números después del decimal.

Prakhar Lohiya
fuente
0

Conceptualmente, se hacen restas repetidas de 256 a su número, hasta que esté en el rango de -128 a +127. Entonces, en su caso, comienza con 132, luego termina con -124 en un solo paso.

Computacionalmente, esto corresponde a extraer los 8 bits menos significativos de su número original. (Y tenga en cuenta que el bit más significativo de estos 8 se convierte en el bit de signo).

Tenga en cuenta que en otros lenguajes este comportamiento no está definido (por ejemplo, C y C ++).

Betsabé
fuente
Para ser claros, el resultado que obtienes es el mismo que si se hicieran restas repetidas. En la práctica, la JVM no lo hace de esta manera. (¡Sería terriblemente ineficiente!)
Stephen C
En efecto. Espero que mi segundo párrafo cubra cómo la JVM realmente hace esto. Pero he jugueteado un poco con mi idioma.
Betsabé
1
Si. ¡El cambio de "esencialmente" a "conceptualmente" hace una gran diferencia!
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           
devil_29
fuente
Se llega a la respuesta correcta por enmascaramiento de bits, no por división y resto.
Marqués de Lorne