Pregunta

¿Por qué Java no incluye soporte para enteros sin signo?

Me parece una extraña omisión, dado que permiten escribir código que es menos probable que produzca desbordamientos en entradas inesperadamente grandes.

Además, el uso de enteros sin signo puede ser una forma de autodocumentación, ya que indican que el valor que se suponía que tenía el int sin signo nunca debería ser negativo.

Por último, en algunos casos, los enteros sin signo pueden ser más eficientes para ciertas operaciones, como la división.

¿Cuál es la desventaja de incluir estos?

¿Fue útil?

Solución

Esto es de una entrevista con Gosling y otros , sobre simplicidad:

  

Gosling: Para mí, como diseñador de idiomas, que en realidad no me considero en estos días, qué "simple" Realmente terminó significando que podría esperar que J. Random Developer tenga la especificación en su cabeza. Esa definición dice que, por ejemplo, Java no lo es, y de hecho, muchos de estos lenguajes terminan con muchos casos de esquina, cosas que nadie entiende realmente. Pregunte a cualquier desarrollador de C sobre unsigned, y muy pronto descubrirá que casi ningún desarrollador de C realmente comprende lo que sucede con unsigned, qué es la aritmética sin signo. Cosas como esas hicieron que C. sea complejo. La parte del lenguaje de Java es, creo, bastante simple. Las bibliotecas que debe buscar.

Otros consejos

Leyendo entre líneas, creo que la lógica era algo así:

  • en general, los diseñadores de Java querían simplificar el repertorio de tipos de datos disponibles
  • para fines cotidianos, consideraron que la necesidad más común era de tipos de datos firmados
  • para implementar ciertos algoritmos, a veces se necesita aritmética sin signo, pero el tipo de programadores que implementarían dichos algoritmos también tendrían el conocimiento para "trabajar". haciendo aritmética sin signo con tipos de datos firmados

Principalmente, diría que fue una decisión razonable. Posiblemente, tendría:

  • hizo byte sin signo, o al menos proporcionó alternativas con signo / sin signo, posiblemente con diferentes nombres, para este tipo de datos (hacer que sea firmado es bueno para la coherencia, pero ¿cuándo necesita un byte firmado?)
  • eliminó 'short' (¿cuándo utilizó la aritmética con signo de 16 bits por última vez?)

Aún así, con un poco de kludging, las operaciones en valores sin signo de hasta 32 bits no son tan malas, y la mayoría de las personas no necesitan división o comparación de 64 bits sin signo.

Esta es una pregunta anterior y Pat mencionó brevemente char, solo pensé que debería ampliar esto para otros que lo verán más adelante. Echemos un vistazo más de cerca a los tipos primitivos de Java:

byte - entero con signo de 8 bits

short - entero de 16 bits con signo

int - entero con signo de 32 bits

long - entero con signo de 64 bits

char - carácter de 16 bits (entero sin signo)

Aunque char no es compatible con la aritmética unsigned , esencialmente puede tratarse como un entero unsigned . Tendría que convertir explícitamente las operaciones aritméticas de nuevo en char , pero le proporciona una forma de especificar números unsigned .

char a = 0;
char b = 6;
a += 1;
a = (char) (a * b);
a = (char) (a + b);
a = (char) (a - 16);
b = (char) (b % 3);
b = (char) (b / a);
//a = -1; // Generates complier error, must be cast to char
System.out.println(a); // Prints ? 
System.out.println((int) a); // Prints 65532
System.out.println((short) a); // Prints -4
short c = -4;
System.out.println((int) c); // Prints -4, notice the difference with char
a *= 2;
a -= 6;
a /= 3;
a %= 7;
a++;
a--;

Sí, no hay soporte directo para enteros sin signo (obviamente, no tendría que volver a convertir la mayoría de mis operaciones en char si hubiera soporte directo). Sin embargo, ciertamente existe un tipo de datos primitivo sin signo. También me gustaría haber visto un byte sin firmar, pero supongo que duplicar el costo de la memoria y usar char es una opción viable.


Editar

Con JDK8 hay nuevas API para Long y Entero que proporciona métodos auxiliares al tratar los valores long e int como valores sin signo.

  • compareUnsigned
  • divideUnsigned
  • parseUnsignedInt
  • parseUnsignedLong
  • remainderUnsigned
  • toUnsignedLong
  • toUnsignedString

Además, Guava proporciona una serie de métodos auxiliares para hacer cosas similares para los tipos enteros lo que ayuda a cerrar la brecha que deja la falta de compatibilidad nativa para enteros unsigned .

Java tiene tipos sin signo, o al menos uno: char es un corto sin signo. Entonces, cualquier excusa que arroje Gosling es en realidad solo su ignorancia por qué no hay otros tipos sin firmar.

También tipos cortos: los cortos se usan todo el tiempo para multimedia. La razón es que puede ajustar 2 muestras en un solo largo de 32 bits sin signo y vectorizar muchas operaciones. Lo mismo con datos de 8 bits y byte sin signo. Puede colocar 4 u 8 muestras en un registro para vectorizar.

Tan pronto como las entradas firmadas y sin firmar se mezclan en una expresión, las cosas comienzan a complicarse y probablemente perderá información. Restringir Java a ints firmados solo aclara las cosas. Me alegro de no tener que preocuparme por todo el negocio firmado / no firmado, aunque a veces extraño el octavo bit en un byte.

http://skeletoncoder.blogspot.com/ 2006/09 / java-tutorials-why-no-unsigned.html

Este tipo dice porque el estándar C define operaciones que involucran entradas sin firmar y firmadas para ser tratadas como sin firmar. Esto podría hacer que los enteros con signo negativo se conviertan en un int grande sin signo, lo que podría causar errores.

Creo que Java está bien como está, agregarlo sin firmar lo complicaría sin mucha ganancia. Incluso con el modelo entero simplificado, la mayoría de los programadores de Java no saben cómo se comportan los tipos numéricos básicos: solo lea el libro Java Puzzlers para ver qué ideas falsas podrías tener.

En cuanto a consejos prácticos:

  • Si sus valores tienen un tamaño algo arbitrario y no encajan en int , use long . Si no encajan en long use BigInteger.

  • Utilice los tipos más pequeños solo para matrices cuando necesite ahorrar espacio.

  • Si necesita exactamente 64/32/16/8 bits, use long / int / short / byte y deje de preocuparse por el bit de signo, excepto por división, comparación, desplazamiento a la derecha y fundición.

Consulte también esta respuesta sobre " portar un generador de números aleatorios de C a Java " ;.

Con JDK8 tiene cierto soporte para ellos.

Todavía podemos ver soporte completo de tipos sin firmar en Java a pesar de las preocupaciones de Gosling.

Sé que esta publicación es demasiado antigua; sin embargo, para su interés, en Java 8 y versiones posteriores, puede usar el tipo de datos int para representar un número entero de 32 bits sin signo, que tiene un valor mínimo de 0 y un valor máximo de 2 32 & # 8722; 1. Use la clase Integer para usar el tipo de datos int como un entero sin signo y métodos estáticos como compareUnsigned () , divideUnsigned () etc. se han agregado a la clase Integer para admitir las operaciones aritméticas para enteros sin signo.

He escuchado historias de que se incluirían cerca de la versión original de Java. Oak fue el precursor de Java, y en algunos documentos de especificaciones se mencionaron valores firmados. Desafortunadamente, estos nunca llegaron al lenguaje Java. Hasta donde alguien ha podido darse cuenta de que simplemente no se implementaron, probablemente debido a una limitación de tiempo.

Una vez tomé un curso de C ++ con alguien en el comité de estándares de C ++ que implicaba que Java tomó la decisión correcta para evitar tener enteros sin signo porque (1) la mayoría de los programas que usan enteros sin signo pueden funcionar igual de bien con enteros con signo y esto es más natural en términos de cómo piensan las personas, y (2) el uso de enteros sin signo resulta en muchos problemas fáciles de crear pero difíciles de depurar, como el desbordamiento aritmético de enteros y la pérdida de bits significativos al convertir entre tipos con signo y sin signo. Si restas por error 1 de 0 usando enteros con signo, a menudo hace que tu programa se bloquee más rápidamente y hace que sea más fácil encontrar el error que si se ajusta a 2 ^ 32 - 1, y los compiladores y las herramientas de análisis estático y las comprobaciones de tiempo de ejecución tienen que suponga que sabe lo que está haciendo ya que eligió usar aritmética sin signo. Además, los números negativos como -1 a menudo pueden representar algo útil, como un campo que se ignora / omite / desarma, mientras que si usara sin signo, tendría que reservar un valor especial como 2 ^ 32 - 1 o algo similar.

Hace mucho tiempo, cuando la memoria era limitada y los procesadores no funcionaban automáticamente en 64 bits a la vez, cada bit contaba mucho más, por lo que tener bytes o cortos firmados o no firmados realmente importaba mucho más a menudo y obviamente era la decisión de diseño correcta. . Hoy en día, solo usar un int firmado es más que suficiente en casi todos los casos de programación regular, y si su programa realmente necesita usar valores mayores que 2 ^ 31 - 1, a menudo solo desea un largo de todos modos. Una vez que estás en el territorio del uso de largos, es aún más difícil encontrar una razón por la que realmente no puedes sobrevivir con 2 ^ 63-1 enteros positivos. Siempre que vayamos a procesadores de 128 bits, será aún menos problemático.

Tu pregunta es " ¿Por qué Java no admite ints sin firmar " ;?

Y mi respuesta a su pregunta es que Java quiere que todos sus tipos primitivos: byte , char , short , int y long deben tratarse como byte , word , dword y qword respectivamente, exactamente igual que en el ensamblado, y los operadores Java son operaciones firmadas en todos sus tipos primitivos, excepto char , pero solo en char solo están sin firmar de 16 bits.

Entonces, los métodos estáticos suponen ser las operaciones sin firmar también para 32 y 64 bits.

Necesita la clase final, cuyos métodos estáticos se pueden llamar para las operaciones sin firmar .

Puedes crear esta clase final, llamarla como quieras e implementar sus métodos estáticos.

Si no tiene idea de cómo implementar los métodos estáticos, entonces este enlace puede ayudarte.

En mi opinión, Java no es similar a C ++ en absoluto , si ni admite tipos sin firmar ni sobrecarga del operador, por lo que creo que Java debería tratarse como un lenguaje completamente diferente tanto de C ++ como de C.

Por cierto, también es completamente diferente en el nombre de los idiomas.

Por lo tanto, no recomiendo en Java escribir código similar a C y no recomiendo escribir código similar a C ++ en absoluto, porque entonces en Java no podrá hacer lo que quiere hacer a continuación en C ++, es decir, el código no seguirá siendo C ++ como para nada y para mí es malo codificar así, cambiar el estilo en el medio.

Recomiendo escribir y usar métodos estáticos también para las operaciones firmadas, de modo que no vea en la combinación de códigos de operadores y métodos estáticos tanto para operaciones firmadas como no firmadas, a menos que solo necesite operaciones firmadas en el código, y está bien usar solo los operadores.

También recomiendo evitar usar los tipos primitivos cortos , int y largos , y usar palabra , < strong> dword y qword respectivamente, y se trata de llamar a los métodos estáticos para operaciones sin firmar y / o operaciones firmadas en lugar de usar operadores.

Si está a punto de realizar operaciones firmadas solamente y usa los operadores solo en el código, entonces está bien usar estos tipos primitivos cortos , int y largo .

En realidad, palabra , dword y qword n't existen en el idioma, pero puede crear nueva clase para cada uno y la implementación de cada uno debería ser muy fácil:

La palabra clase contiene el tipo primitivo corto solamente, la clase dword contiene el tipo primitivo int solamente y la clase qword contiene el tipo primitivo largo solamente. Ahora todos los métodos sin signo y con signo como estáticos o no como su elección, puede implementar en cada clase, es decir, todas las operaciones de 16 bits, tanto sin signo como firmadas, dando nombres de significado en la clase palabra , todas las operaciones de 32 bits sin signo y firmadas dando nombres de significado en la clase dword y todas las operaciones de 64 bits sin signo y firmadas dando nombres de significado en la clase qword .

Si no le gusta dar demasiados nombres diferentes para cada método, siempre puede usar la sobrecarga en Java, ¡es bueno leer que Java no lo eliminó también!

Si desea métodos en lugar de operadores para 8 bi

Porque el tipo unsigned es puro mal.

El hecho de que en C unsigned - int produce unsigned es aún más malo.

Aquí hay una instantánea del problema que me quemó más de una vez:

// We have odd positive number of rays, 
// consecutive ones at angle delta from each other.
assert( rays.size() > 0 && rays.size() % 2 == 1 );

// Get a set of ray at delta angle between them.
for( size_t n = 0; n < rays.size(); ++n )
{
    // Compute the angle between nth ray and the middle one.
    // The index of the middle one is (rays.size() - 1) / 2,
    // the rays are evenly spaced at angle delta, therefore
    // the magnitude of the angle between nth ray and the 
    // middle one is: 
    double angle = delta * fabs( n - (rays.size() - 1) / 2 ); 

    // Do something else ...
}

¿Ya has notado el error? Confieso que solo lo vi después de intervenir con el depurador.

Debido a que n es de tipo sin signo size_t , toda la expresión n - (rays.size () - 1) / 2 se evalúa como < code> unsigned . Se pretende que esa expresión sea una posición firmada del rayo n del medio: el primer rayo del medio en el lado izquierdo tendría la posición -1, el primero a la derecha tendría la posición +1, etc. Después de tomar el valor de abs y multiplicarlo por el ángulo delta , obtendría el ángulo entre el rayo n y el centro uno.

Desafortunadamente para mí, la expresión anterior contenía el mal sin signo y en lugar de evaluar, digamos -1, se evaluó a 2 ^ 32-1. La conversión posterior a double selló el error.

Después de uno o dos errores causados ??por el mal uso de la aritmética unsigned , uno debe comenzar a preguntarse si el bit extra que se obtiene vale la pena. Estoy intentando, en la medida de lo posible, evitar el uso de tipos unsigned en aritmética, aunque todavía lo uso para operaciones no aritméticas, como máscaras binarias.

Hay algunas gemas en la especificación 'C' que Java dejó caer por razones pragmáticas pero que lentamente se están arrastrando hacia atrás con la demanda del desarrollador (cierres, etc.).

Menciono una primera porque está relacionada con esta discusión; La adherencia de los valores de puntero a la aritmética de enteros sin signo. Y, en relación con este tema del hilo, la dificultad de mantener la semántica sin firmar en el mundo firmado de Java.

Supongo que si uno obtuviera un alter ego de Dennis Ritchie para asesorar al equipo de diseño de Gosling, habría sugerido darle a Signed un '' cero en el infinito '', de modo que todas las solicitudes de compensación de dirección agreguen primero su TAMAÑO DE ANILLO ALGEBRAICO para obviar valores negativos.

De esa manera, cualquier desplazamiento arrojado a la matriz nunca puede generar un SEGFAULT. Por ejemplo, en una clase encapsulada a la que llamo RingArray de dobles que necesita un comportamiento sin signo, en "ciclo de rotación automática". contexto:

// ...
// Housekeeping state variable
long entrycount;     // A sequence number
int cycle;           // Number of loops cycled
int size;            // Active size of the array because size<modulus during cycle 0
int modulus;         // Maximal size of the array

// Ring state variables
private int head;   // The 'head' of the Ring
private int tail;   // The ring iterator 'cursor'
// tail may get the current cursor position
// and head gets the old tail value
// there are other semantic variations possible

// The Array state variable
double [] darray;    // The array of doubles

// somewhere in constructor
public RingArray(int modulus) {
    super();
    this.modulus = modulus;
    tail =  head =  cycle = 0;
    darray = new double[modulus];
// ...
}
// ...
double getElementAt(int offset){
    return darray[(tail+modulus+offset%modulus)%modulus];
}
//  remember, the above is treating steady-state where size==modulus
// ...

El RingArray anterior nunca '' obtendría '' de un índice negativo, incluso si un solicitante malintencionado lo intentara. Recuerde, también hay muchas solicitudes legítimas para solicitar valores de índice anteriores (negativos).

NB: el% módulo externo desreferencia las solicitudes legítimas, mientras que el módulo% interno oculta la malicia evidente de los negativos más negativos que el módulo. Si esto alguna vez apareciera en un Java + .. + 9 || 8 + .. + espec., Entonces el problema se convertiría realmente en un 'programador que no puede' auto-rotar '' FALLO '.

Estoy seguro de que la llamada 'deficiencia' int sin signo de Java se puede compensar con la línea anterior.

PD: Solo para dar contexto al mantenimiento de RingArray anterior, aquí hay una operación candidata 'set' para que coincida con la operación del elemento 'get' anterior:

void addElement(long entrycount,double value){ // to be called only by the keeper of entrycount
    this.entrycount= entrycount;
    cycle = (int)entrycount/modulus;
    if(cycle==0){                       // start-up is when the ring is being populated the first time around
        size = (int)entrycount;         // during start-up, size is less than modulus so use modulo size arithmetic
        tail = (int)entrycount%size;    //  during start-up
    }
    else {
        size = modulus;
        head = tail;
        tail = (int)entrycount%modulus; //  after start-up
    }
    darray[head] = value;               //  always overwrite old tail
}

Puedo pensar en un desafortunado efecto secundario. En las bases de datos incrustadas de Java, la cantidad de identificadores que puede tener con un campo de identificación de 32 bits es 2 ^ 31, no 2 ^ 32 (~ 2 billones, no ~ 4 billones).

La razón en mi humilde opinión es porque son / eran demasiado flojos para implementar / corregir ese error. Sugerir que los programadores de C / C ++ no entiendan sin firmar, estructura, unión, indicador de bits ... es simplemente absurdo.

Ether estabas hablando con un programador básico / bash / java a punto de comenzar a programar a la C, sin ningún conocimiento real de este lenguaje o simplemente estás hablando por tu propia cuenta. ;)

cuando tratas todos los días en formato, ya sea desde archivos o hardware, comienzas a cuestionar qué demonios estaban pensando.

Un buen ejemplo aquí sería tratar de usar un byte sin signo como un bucle giratorio automático. Para aquellos de ustedes que no entienden la última oración, cómo demonios se llaman programadores.

DC

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top