¿Por qué la conversión de float a double cambia el valor?

He estado tratando de descubrir el motivo, pero no pude. Alguien puede ayudarme?

mira el siguiente ejemplo.

float f; f = 125.32f; System.out.println("value of f = " + f); double d = (double) 125.32f; System.out.println("value of d = " + d); 

Este es el resultado:

valor de f = 125.32

valor de d = 125.31999969482422

    1. Cuando convierte un float en un double , no hay pérdida de información. Cada float se puede representar exactamente como un double .
    2. Por otro lado, ninguna representación decimal impresa por System.out.println es el valor exacto para el número. Una representación decimal exacta podría requerir hasta aproximadamente 760 dígitos decimales. En cambio, System.out.println imprime exactamente el número de dígitos decimales que permiten analizar la representación decimal de nuevo en el float original o en el double . Hay más double , por lo que al imprimir uno, System.out.println necesita imprimir más dígitos antes de que la representación sea inequívoca.

    El valor de un float no cambia cuando se convierte en un double . Hay una diferencia en los números mostrados porque se requieren más dígitos para distinguir un valor double de sus vecinos, lo cual es requerido por la documentación de Java . Esa es la documentación de toString , a la que se hace referencia (a través de varios enlaces) desde la documentación de println .

    El valor exacto para 125.32f es 125.31999969482421875. Los dos valores de float adyacentes son 125.3199920654296875 y 125.32000732421875. Observe que 125.32 está más cerca de 125.31999969482421875 que a cualquiera de los vecinos. Por lo tanto, al mostrar “125.32”, Java ha mostrado suficientes dígitos para que la conversión desde el número decimal a float reproduzca el valor del float pasado a println .

    Los dos valores double vecinos de 125.31999969482421875 son 125.3199996948242045391452847979962825775146484375 y 125.3199996948242329608547152020037174224853515625. Observe que 125.32 está más cerca del último vecino que del valor original. Por lo tanto, imprimir “125.32” no contiene suficientes dígitos para distinguir el valor original. Java debe imprimir más dígitos para garantizar que una conversión del número mostrado al double vuelva a reproducir el valor del double pasado a println .

    La conversión de float a double es una conversión de ampliación , tal como lo especifica el JLS . Una conversión de ampliación se define como un mapeo inyectivo de un conjunto más pequeño en su superconjunto. Por lo tanto, el número que se representa no cambia después de una conversión de float a double .

    Más información con respecto a su pregunta actualizada

    En su actualización, agregó un ejemplo que se supone debe demostrar que el número ha cambiado. Sin embargo, solo muestra que la representación de cadena del número ha cambiado, lo que de hecho tiene debido a la precisión adicional adquirida a través de la conversión a double . Tenga en cuenta que su primera salida es solo un redondeo de la segunda salida. Según lo especificado por Double.toString ,

    Debe haber al menos un dígito para representar la parte fraccionaria, y más allá de eso tantos, pero solo tantos, más dígitos como sean necesarios para distinguir de manera única el valor del argumento de los valores adyacentes de tipo double .

    Como los valores adyacentes en el tipo double están mucho más cerca que en float , se necesitan más dígitos para cumplir con esa regla.

    El número de punto flotante IEEE-754 de 32 bits más cercano a 125.32 es, de hecho, 125.31999969482421875. Muy cerca, pero no del todo allí (eso es porque 0.32 se repite en binario).

    Cuando lo lanzas a un doble, es el valor 125.31999969482421875 que se convertirá en un doble (125.32 no se encuentra en este punto, la información que realmente debería terminar en .32 está completamente perdida) y por supuesto puede ser representada exactamente por un doble. Cuando imprime ese doble, la rutina de impresión cree que tiene dígitos más significativos de lo que realmente tiene (pero, por supuesto, no puede saberlo), por lo que se imprime a 125.31999969482422, que es el decimal más corto que redondea a ese doble exacto (y de todos los decimales de esa longitud, es el más cercano).

    El problema de la precisión de los números de coma flotante es realmente independiente del idioma, por lo que usaré MATLAB en mi explicación.

    La razón por la que ve la diferencia es que ciertos números no son exactamente representables en un número fijo de bits. Tome 0.1 por ejemplo:

     >> format hex >> double(0.1) ans = 3fb999999999999a >> double(single(0.1)) ans = 3fb99999a0000000 

    Por lo tanto, el error en la aproximación de 0.1 en precisión simple aumenta cuando lo lanza como número de coma flotante de precisión doble. El resultado es diferente de su aproximación si comenzó directamente con precisión doble.

     >> double(single(0.1)) - double(0.1) ans = 1.490116113833651e-09 

    Como ya se explicó, todos los flotantes se pueden representar exactamente como un doble y el motivo de su problema es que System.out.println realiza un redondeo al mostrar el valor de un float o double pero la metodología de redondeo no es la misma en ambos casos.

    Para ver el valor exacto de la flotación, puede usar un BigDecimal :

     float f = 125.32f; System.out.println("value of f = " + new BigDecimal(f)); double d = (double) 125.32f; System.out.println("value of d = " + new BigDecimal(d)); 

    qué salidas:

     value of f = 125.31999969482421875 value of d = 125.31999969482421875 

    no funcionará en Java porque en java por defecto tomará los valores reales como el doble y si declaramos un valor flotante sin representación flotante como 123.45f por defecto, lo tomará como el doble y causará un error como pérdida de precisión

    La representación de los valores cambia debido a los contratos de los métodos que convierten los valores numéricos en una String , correspondientemente java.lang.Float#toString(float) y java.lang.Double#toString(double) , mientras que el valor real permanece igual . Hay una parte común en Javadoc de ambos métodos antes mencionados que elabora requisitos para la representación de String valores:

    Debe haber al menos un dígito para representar la parte fraccionaria, y más allá de eso tantos, pero solo tantos, más dígitos como sean necesarios para distinguir de manera única el valor del argumento de los valores adyacentes

    Para ilustrar la similitud de las partes significativas para los valores de ambos tipos, se puede ejecutar el siguiente fragmento:

     package com.my.sandbox.numbers; public class FloatToDoubleConversion { public static void main(String[] args) { float f = 125.32f; floatToBits(f); double d = (double) f; doubleToBits(d); } private static void floatToBits(float floatValue) { System.out.println(); System.out.println("Float."); System.out.println("String representation of float: " + floatValue); int bits = Float.floatToIntBits(floatValue); int sign = bits >>> 31; int exponent = (bits >>> 23 & ((1 < < 8) - 1)) - ((1 << 7) - 1); int mantissa = bits & ((1 << 23) - 1); System.out.println("Bytes: " + Long.toBinaryString(Float.floatToIntBits(floatValue))); System.out.println("Sign: " + Long.toBinaryString(sign)); System.out.println("Exponent: " + Long.toBinaryString(exponent)); System.out.println("Mantissa: " + Long.toBinaryString(mantissa)); System.out.println("Back from parts: " + Float.intBitsToFloat((sign << 31) | (exponent + ((1 << 7) - 1)) << 23 | mantissa)); System.out.println(10D); } private static void doubleToBits(double doubleValue) { System.out.println(); System.out.println("Double."); System.out.println("String representation of double: " + doubleValue); long bits = Double.doubleToLongBits(doubleValue); long sign = bits >>> 63; long exponent = (bits >>> 52 & ((1 < < 11) - 1)) - ((1 << 10) - 1); long mantissa = bits & ((1L << 52) - 1); System.out.println("Bytes: " + Long.toBinaryString(Double.doubleToLongBits(doubleValue))); System.out.println("Sign: " + Long.toBinaryString(sign)); System.out.println("Exponent: " + Long.toBinaryString(exponent)); System.out.println("Mantissa: " + Long.toBinaryString(mantissa)); System.out.println("Back from parts: " + Double.longBitsToDouble((sign << 63) | (exponent + ((1 << 10) - 1)) << 52 | mantissa)); } } 

    En mi entorno, el resultado es:

     Float. String representation of float: 125.32 Bytes: 1000010111110101010001111010111 Sign: 0 Exponent: 110 Mantissa: 11110101010001111010111 Back from parts: 125.32 Double. String representation of double: 125.31999969482422 Bytes: 100000001011111010101000111101011100000000000000000000000000000 Sign: 0 Exponent: 110 Mantissa: 1111010101000111101011100000000000000000000000000000 Back from parts: 125.31999969482422 

    De esta forma, puede ver que el signo de los valores, el exponente son los mismos, mientras que su mantisa se extendió conservando su parte significativa ( 11110101010001111010111 ) exactamente igual.

    La lógica de extracción utilizada de las partes numéricas de coma flotante: 1 y 2 .

    Ambos son lo que Microsoft llama “tipos de datos de números aproximados”.

    Hay una razón. Un flotador tiene una precisión de 7 dígitos, y un doble 15. Pero he visto que ocurre muchas veces que 8.0 – 1.0 – 6.999999999. Esto se debe a que no se garantiza que representen una fracción numérica decimal exactamente.

    Si necesita precisión absoluta e invariable, vaya con un decimal o tipo integral.