2012-05-17 14 views
7

Estaba intentando rastrear un comportamiento Java muy extraño. Tengo una fórmula que implica un doble, pero está "garantizada" para dar una respuesta entera, específicamente, un entero de 32 bits sin signo (que, por desgracia, Java no funciona bien). Desafortunadamente, mis respuestas fueron a veces incorrectas.¿Los moldes primitivos de tipo entero de Java están "limitados" a MAX_INT del tipo de fundición?

Finalmente encontré el problema, pero el comportamiento es todavía muy extraño para mí: un elenco double directamente a un int parece estar limitado a un máximo MAX_INT para un entero con signo, mientras que un elenco double a un long que es luego emitir a int me da la respuesta esperada (-1; el INT MAXIMO de un entero de 32 bits sin signo representado como un entero de 32 bits con signo).

escribí un pequeño programa de prueba:

public static void main(String[] args) { 
    // This is the Max Int for a 32-bit unsigned integer 
    double maxUIntAsDouble = 4294967295.00; 
    long maxUintFromDoubleAsLong = (long)maxUIntAsDouble; 
    long maxUintFromDoubleAsInt = (int)maxUIntAsDouble; 
    int formulaTest = (int) (maxUintFromDoubleAsLong * 1.0); 
    int testFormulaeWithDoubleCast = (int)((long) (maxUintFromDoubleAsLong * 1.0)); 
    // This is a more-or-less random "big number" 
    long longUnderTest = 4123456789L; 
    // Max int for a 32-bit unsigned integer 
    long longUnderTest2 = 4294967295L; 
    int intFromLong = (int) longUnderTest; 
    int intFromLong2 = (int) longUnderTest2; 
    System.out.println("Long is: " + longUnderTest); 
    System.out.println("Translated to Int is:" + intFromLong); 
    System.out.println("Long 2 is: " + longUnderTest2); 
    System.out.println("Translated to Int is:" + intFromLong2); 
    System.out.println("Max UInt as Double: " + maxUIntAsDouble); 
    System.out.println("Max UInt from Double to Long: " + maxUintFromDoubleAsLong); 
    System.out.println("Max UInt from Double to Int: " + maxUintFromDoubleAsInt); 
    System.out.println("Formula test: " + formulaTest); 
    System.out.println("Formula Test with Double Cast: " + testFormulaeWithDoubleCast); 
} 

Cuando ejecuto este pequeño programa me sale:

Long is: 4123456789 
Translated to Int is:-171510507 
Long 2 is: 4294967295 
Translated to Int is:-1 
Max UInt as Double: 4.294967295E9 
Max UInt from Double to Long: 4294967295 
Max UInt from Double to Int: 2147483647 
// MAX INT for an unsigned int 
Formula test: 2147483647 
// Binary: all 1s, which is what I expected 
Formula Test with Double Cast: -1 

Las dos últimas líneas son las que yo estoy tratando de entender. El doble elenco me da el "-1" esperado; pero el elenco directo me da MAX_INT para un entero con signo de 32 bits. Viniendo de un fondo de C++, entendería si me da un "número impar" en lugar del -1 esperado (también conocido como "ingenuo de fundición"), pero esto me tiene perplejo.

Así, a la pregunta entonces: este comportamiento es "esperado" en Java (por ejemplo, cualquier double fundido directamente a un int será "tope" a MAX_INT)? ¿El casting hace esto para cualquier tipo inesperado? Esperaría que sea similar para short y byte, por ejemplo; pero, ¿cuál es el 'comportamiento esperado' al lanzar un doble grande para flotar?

Gracias!

Respuesta

10

Se espera que esto suceda. Recuerde que no hay tipos largos o int sin signo primarios en Java, y Java Language Specification (Java 7) para la conversión de la primitiva de estrechamiento (5.1.3) establece que emitir un valor de coma flotante "demasiado pequeño o demasiado grande" (ya sea doble o flotante) a un tipo integral de int o largo usará el valor mínimo o máximo de los tipos integrales firmados (énfasis mío):

una conversión estrechamiento de un número de coma flotante a un tipo integral T tiene dos pasos:

  1. En el primer paso, el número de coma flotante se convierte bien a un largo, si T es long, or to a int, si T es byte, short, char o int, como se indica a continuación:

    • Si el número de coma flotante está NaN (§4.2.3), el resultado de la primera etapa de la conversión es un int o largo 0.
    • De lo contrario, si el número de coma flotante no es una infinito, el valor de coma flotante se redondea a un valor entero V, redondeando hacia cero usando el modo IEEE 754 de redondeo a cero (§4.2.3). Entonces hay son dos casos:

      • a. Si T es largo, y este valor entero se puede representar como un largo, entonces el resultado del primer paso es el valor largo V.
      • b. De lo contrario, si este valor entero se puede representar como un entero, entonces el resultado de la primera etapa es el valor int V.
    • De lo contrario, uno de los dos casos siguientes deben ser verdaderas:

      • a. El valor debe ser demasiado pequeño (un valor negativo de gran magnitud o infinito negativo), y el resultado del primer paso es el menor valor representable de tipo int o largo.
      • b. El valor debe ser demasiado grande (un valor positivo de gran magnitud o infinito positivo), y el resultado del primer paso es el mayor valor representable de tipo int o largo. *
  2. En el segundo paso: * Si T es int o largo, el resultado de la conversión es el resultado de la primera etapa. * Si T es byte, char o corto, el resultado de la conversión es el resultado de una reducción a conversión a tipo T (§5.1.3) del resultado del primer paso.

Ejemplo 5.1.3-1. El estrechamiento de conversión Primitive

class Test { 
    public static void main(String[] args) { 
     float fmin = Float.NEGATIVE_INFINITY; 
     float fmax = Float.POSITIVE_INFINITY; 
     System.out.println("long: " + (long)fmin + ".." + (long)fmax); 
     System.out.println("int: " + (int)fmin + ".." + (int)fmax); 
     System.out.println("short: " + (short)fmin + ".." + (short)fmax); 
     System.out.println("char: " + (int)(char)fmin + ".." + (int)(char)fmax); 
     System.out.println("byte: " + (byte)fmin + ".." + (byte)fmax); 
    } 
} 

Este programa produce la salida:

long: -9223372036854775808..9223372036854775807 
int: -2147483648..2147483647 
short: 0..-1 
char: 0..65535 
byte: 0..-1 

Los resultados para char, int, y largo son sorprendente, produciendo los valores máximo representables del tipo y mínimo.

Los resultados de byte y short pierden información sobre el signo y magnitud de los valores numéricos y también pierden precisión.Los resultados pueden entenderse examinando los bits de orden baja del mínimo y máximo int. El mínimo int es, en hexadecimal, 0x80000000, y el máximo int es 0x7fffffff. Esto explica los resultados cortos, que son los 16 bits bajos de estos valores, a saber, 0x0000 y 0xffff; es explica los resultados de char, que también son los 16 bits bajos de estos valores, a saber, '\ u0000' y '\ uffff'; y explica los resultados del byte , que son los 8 bits bajos de estos valores, a saber, 0x00 y 0xff.

El primer caso int formulaTest = (int) (maxUintFromDoubleAsLong * 1.0); por lo tanto promueve maxUintFromDoubleAsLong a una doble vía de multiplicación y luego lo convierte en un int. Como el valor es demasiado grande para representarlo como un entero con signo, el valor pasa a ser 2147483647 (Integer.MAX_VALUE) o 0x7FFFFFFF.

En cuanto a este último caso:

Una conversión estrechamiento de un entero con signo a un tipo integral T simplemente descarta todos, pero los bits de orden n más bajas, donde n es el número de bits utilizados para representar Tipo T. Además de una posible pérdida de información sobre la magnitud del valor numérico, esto puede ocasionar que el signo del valor resultante difiera del signo del valor de entrada.

Así int testFormulaeWithDoubleCast = (int)((long) (maxUintFromDoubleAsLong * 1.0)); primera promueve maxUintFromDoubleAsLong al doble, de vuelta a tiempo (siendo apropiado) y luego a un int. En el último lanzamiento, los bits sobrantes simplemente se descartan, dejándote con 0xFFFFFFFF, que es -1 cuando se interpreta como un entero con signo.

+0

Conocía la falta de "unsigned" en Java (una de las cosas que hace que Java sea tan doloroso cuando se trabaja con protocolos orientados a bits), pero la mayor parte del resto es completamente nueva. Definitivamente no habría esperado ese comportamiento de "corto" y "byte". Gracias. :RE –

3

Esta es la forma en que se escribe la especificación de idioma. Convirtiendo un punto flotante en un tipo entero, si el valor es demasiado grande para el destino, entonces se sustituye el valor máximo. En una conversión de estrechamiento de un tipo entero a uno más pequeño, los bits de orden superior se descartan.

Véase el JLS 5.1.3. Narrowing Primitive Conversion

Por lo tanto, la respuesta a la pregunta del título es "sí".

Cuestiones relacionadas