Converti float in double senza perdere precisione


97

Ho un float primitivo e ho bisogno di un double primitivo. Il semplice lancio del galleggiante per raddoppiare mi dà una strana precisione extra. Per esempio:

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

Tuttavia, se invece di eseguire il casting, eseguo l'output del float come una stringa e analizzo la stringa come un double, ottengo quello che voglio:

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

C'è un modo migliore che andare a String e tornare indietro?

Risposte:


124

Non è che tu stia effettivamente ottenendo una precisione extra, è che il galleggiante non rappresentava accuratamente il numero a cui stavi mirando originariamente. Il doppio è rappresentando il galleggiante originale con precisione; toStringmostra i dati "extra" che erano già presenti.

Ad esempio (e questi numeri non sono corretti, sto solo inventando delle cose) supponiamo che tu abbia:

float f = 0.1F;
double d = f;

Quindi il valore di fpotrebbe essere esattamente 0,100000234523. davrà esattamente lo stesso valore, ma quando lo converti in una stringa "si fiderà" che è accurato con una precisione maggiore, quindi non verrà arrotondato prima, e vedrai le "cifre extra" che erano già lì, ma nascosto da te.

Quando converti in una stringa e viceversa, ti ritroverai con un valore doppio che è più vicino al valore della stringa rispetto al float originale, ma è utile solo se credi davvero che il valore della stringa sia quello che volevi veramente.

Sei sicuro che float / double siano i tipi appropriati da usare qui invece di BigDecimal? Se stai cercando di utilizzare numeri che hanno valori decimali precisi (ad esempio denaro), allora BigDecimalè un tipo più appropriato IMO.


40

Trovo che la conversione alla rappresentazione binaria sia più semplice per comprendere questo 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)));

Puoi vedere che il float viene espanso al doppio aggiungendo 0 alla fine, ma che la doppia rappresentazione di 0,27 è "più accurata", da qui il problema.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

25

Ciò è dovuto al contratto di Float.toString(float), che dice in parte:

Quante cifre devono essere stampate per la parte frazionaria […]? Deve essere presente almeno una cifra per rappresentare la parte frazionaria, e oltre a quella tante, ma solo tante, più cifre quante sono necessarie per distinguere in modo univoco il valore dell'argomento dai valori adiacenti di tipo float. Cioè, supponiamo che x sia il valore matematico esatto rappresentato dalla rappresentazione decimale prodotta da questo metodo per un argomento finito diverso da zero f. Allora f deve essere il valore float più vicino a x; oppure, se due valori float sono ugualmente vicini a x, allora f deve essere uno di questi e il bit meno significativo del significato di f deve essere 0.


13

Ho riscontrato questo problema oggi e non ho potuto utilizzare il refactoring su BigDecimal, perché il progetto è davvero enorme. Tuttavia ho trovato la soluzione usando

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

E questo funziona.

Notare che la chiamata a result.doubleValue () restituisce 5623.22998046875

Ma chiamando doubleResult.doubleValue () restituisce correttamente 5623.23

Ma non sono del tutto sicuro che sia una soluzione corretta.


1
Ha funzionato per me. È anche molto veloce. Non sono sicuro del motivo per cui questo non è contrassegnato come risposta.
Sameer

Questo è il metodo che uso e non hai bisogno della nuova parte Float ... Crea semplicemente FloatingDecimal con la primitiva. Sarà automaticamente inscatolato ... E funziona anche velocemente ... C'è uno svantaggio, FloatingDecimal è immutabile, quindi devi crearne uno per ogni singolo float ... immagina un codice computazionale di O (1e10)! !! Ovviamente BigDecimal ha lo stesso inconveniente ...
Mostafa Zeinali

6
Lo svantaggio di questa soluzione è che sun.misc.FloatingDecimal è la classe interna di JVM e la firma del suo costruttore è stata modificata in Java 1.8. Nessuno dovrebbe usare classi interne in applicazioni nella vita reale.
Igor Bljahhin

8

Ho trovato la seguente soluzione:

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

Se usi float e double invece di Float e Double usa quanto segue:

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

7

Usa un al BigDecimalposto di float/ double. Ci sono molti numeri che non possono essere rappresentati come virgola mobile binaria (ad esempio 0.1). Quindi devi sempre arrotondare il risultato a una precisione nota o usare BigDecimal.

Vedi http://en.wikipedia.org/wiki/Floating_point per ulteriori informazioni.


Supponiamo che tu abbia 10 milioni di prezzi di scambio fluttuanti nella tua cache, prendi ancora in considerazione l'utilizzo di BigDecimal e istanziare univoco diciamo ~ 6 milioni di oggetti (per scontare peso mosca / immutabile) .. o c'è di più?
Sendi_t

1
@Sendi_t Per favore, non usare commenti per porre domande complesse :-)
Aaron Digulla

Grazie per aver guardato! .. usiamo i galleggianti in questo momento, come è stato concordato un decennio fa - stavo cercando di vedere il tuo punto di vista su questa situazione in seguito -. Grazie!
Sendi_t

@Sendi_t Misunderstanding. Non posso rispondere alla tua domanda in 512 caratteri. Si prega di fare una domanda corretta e di inviarmi un collegamento.
Aaron Digulla

1
@ GKFX Non esiste una "taglia unica" quando si tratta di gestire i decimali sui computer. Ma dal momento che la maggior parte delle persone non lo capisce, li indico BigDecimalperché ciò rileverà la maggior parte degli errori comuni. Se le cose sono troppo lente, devono imparare di più e trovare modi per ottimizzare i loro problemi. L'ottimizzazione prematura è la radice di tutti i mali: DE Knuth.
Aaron Digulla

1

I galleggianti, per natura, sono imprecisi e hanno sempre "problemi" di arrotondamento accurati. Se la precisione è importante, potresti prendere in considerazione il refactoring dell'applicazione per utilizzare Decimal o BigDecimal.

Sì, i float sono computazionalmente più veloci dei decimali grazie al supporto del processore attivo. Tuttavia, vuoi veloce o preciso?


1
Anche l'aritmetica decimale è inesatta. (es. 1/3 * 3 == 0.9999999999999999999999999999) Ovviamente è meglio per rappresentare quantità decimali esatte come il denaro, ma per le misurazioni fisiche non ha alcun vantaggio.
dan04

2
Ma 1 == 0.9999999999999999999999999999 :)
Emmanuel Bourg

0

Per informazioni questo rientra nell'articolo 48 - Evita float e double quando sono richiesti valori esatti, di Effective Java 2nd edition di Joshua Bloch. Questo libro è pieno zeppo di cose buone e merita sicuramente una visita.


0

funziona?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;

0

Una soluzione semplice che funziona bene è analizzare il double dalla rappresentazione di stringa del float:

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

Non super efficiente, ma funziona!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.