Convierta el flotador en doble sin perder precisión

97

Tengo un flotador primitivo y lo necesito como doble primitivo. Simplemente lanzar el flotador al doble me da una precisión extra extraña. Por ejemplo:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

Sin embargo, si en lugar de emitir, obtengo el flotador como una cadena y analizo la cadena como un doble, obtengo lo que quiero:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

¿Hay una mejor manera que ir a String y volver?

Steve Armstrong
fuente

Respuestas:

124

No es que usted está realmente conseguir precisión adicional - es que el flotador no representa con precisión el número al que se proponían originalmente. La doble está representando el flotador original con exactitud;toStringmuestra los datos "extra" que ya estaban presentes.

Por ejemplo (y estos números no son correctos, solo estoy inventando cosas) suponga que tiene:

float f = 0.1F;
double d = f;

Entonces el valor de fpodría ser exactamente 0,100000234523. dtendrá exactamente el mismo valor, pero cuando lo convierta en una cadena, "confiará" en que es exacto con una precisión mayor, por lo que no se redondeará tan pronto, y verá los "dígitos adicionales" que ya estaban allí, pero escondido de ti.

Cuando convierte a una cadena y viceversa, termina con un valor doble que está más cerca del valor de la cadena que el flotante original, pero eso solo es bueno si realmente cree que el valor de la cadena es lo que realmente quería.

¿Estás seguro de que float / double son los tipos apropiados para usar aquí en lugar de BigDecimal? Si está tratando de usar números que tienen valores decimales precisos (por ejemplo, dinero), entonces BigDecimales un tipo IMO más apropiado.

Jon Skeet
fuente
40

Encuentro que la conversión a la representación binaria es más fácil de comprender este problema.

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

Puede ver que el flotador se expande al doble agregando 0 al final, pero que la representación doble de 0.27 es 'más precisa', de ahí el problema.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
Brecht Yperman
fuente
25

Esto se debe al contrato de Float.toString(float), que dice en parte:

¿Cuántos dígitos deben imprimirse para la parte fraccionaria […]? 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 forma única el valor del argumento de los valores adyacentes de tipo float. Es decir, suponga que x es el valor matemático exacto representado por la representación decimal producida por este método para un argumento f finito distinto de cero. Entonces f debe ser el valor flotante más cercano a x; o, si dos valores flotantes están igualmente cerca de x, entonces f debe ser uno de ellos y el bit menos significativo del significado de f debe ser 0.

erickson
fuente
13

Hoy me encontré con este problema y no pude usar la refactorización a BigDecimal, porque el proyecto es realmente enorme. Sin embargo, encontré una solución usando

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

Y esto funciona.

Tenga en cuenta que llamar a result.doubleValue () devuelve 5623.22998046875

Pero llamar a doubleResult.doubleValue () devuelve correctamente 5623.23

Pero no estoy del todo seguro de si es una solución correcta.

Andrés
fuente
1
Trabajó para mi. También es muy rápido. No estoy seguro de por qué esto no está marcado como respuesta.
Sameer
Este es el método que uso, y no necesitas la nueva parte Float ... Simplemente crea FloatingDecimal con la primitiva. Se empaquetará automáticamente ... Y también funciona rápido ... Hay un inconveniente, FloatingDecimal es inmutable, por lo que debes crear uno para cada flotante ... ¡imagina un código computacional de O (1e10)! !! Por supuesto, BigDecimal tiene el mismo inconveniente ...
Mostafa Zeinali
6
El inconveniente de esta solución es que sun.misc.FloatingDecimal es la clase interna de JVM y la firma de su constructor se ha cambiado en Java 1.8. Nadie debería usar clases internas en aplicaciones de la vida real.
Igor Bljahhin
8

Encontré la siguiente solución:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Si usa float y double en lugar de Float y Double, use lo siguiente:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}
GSD.Aaz
fuente
7

Utilice un en BigDecimallugar de float/ double. Hay muchos números que no se pueden representar como punto flotante binario (por ejemplo, 0.1). Por lo tanto, siempre debe redondear el resultado a una precisión conocida o usarBigDecimal .

Consulte http://en.wikipedia.org/wiki/Floating_point para obtener más información.

Aaron Digulla
fuente
Digamos que tiene 10 millones de precios de comercio flotante en su caché, ¿todavía considera usar BigDecimal e instanciar objetos únicos, digamos ~ 6 millones (para descontar el peso mosca / inmutable) ... o hay más?
Sendi_t
1
@Sendi_t Por favor, no utilice comentarios para hacer preguntas complejas :-)
Aaron Digulla
¡Gracias por mirar! ... usamos flotadores ahora mismo como se acordó hace una década - estaba tratando de ver tu punto de vista sobre esta situación en el seguimiento -. ¡Gracias!
Sendi_t
@Sendi_t Malentendido. No puedo responder tu pregunta en 512 caracteres. Haga una pregunta adecuada y envíeme un enlace.
Aaron Digulla
1
@GKFX No existe una "talla única" cuando se trata de manejar decimales en computadoras. Pero dado que la mayoría de la gente no lo entiende, les señalo BigDecimalya que eso detectará la mayoría de los errores comunes. Si las cosas van demasiado lentas, necesitan aprender más y encontrar formas de optimizar sus problemas. La optimización prematura es la raíz de todos los males: DE Knuth.
Aaron Digulla
1

Los flotadores, por naturaleza, son imprecisos y siempre tienen "problemas" de redondeo. Si la precisión es importante, entonces podría considerar refactorizar su aplicación para usar Decimal o BigDecimal.

Sí, los flotadores son computacionalmente más rápidos que los decimales debido a la compatibilidad con el procesador. Sin embargo, ¿quieres rápido o preciso?

Yo no
fuente
1
La aritmética decimal también es inexacta. (por ejemplo, 1/3 * 3 == 0.999999999999999999999999999999) Es, por supuesto, mejor para representar cantidades decimales exactas como dinero, pero para medidas físicas no tiene ninguna ventaja.
dan04
2
Pero 1 == 0.9999999999999999999999999999 :)
Emmanuel Bourg
0

Para obtener información, esto se incluye en el Artículo 48: Evite la flotación y el doble cuando se requieren valores exactos, de Effective Java 2nd edition de Joshua Bloch. Este libro está repleto de cosas buenas y definitivamente vale la pena echarle un vistazo.

mR_fr0g
fuente
0

¿Esto funciona?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
AesmaDiv
fuente
0

Una solución simple que funciona bien es analizar el doble de la representación de cadena del flotador:

double val = Double.valueOf(String.valueOf(yourFloat));

No es muy eficiente, ¡pero funciona!

Alessandro Roaro
fuente