Come sapere se un BigDecimal può esattamente convertire in float o doppio?


10

La classe BigDecimalha alcuni metodi utili per garantire la conversione senza perdita di dati:

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

Tuttavia, i metodi floatValueExact()e doubleValueExact()non esistono.

Ho letto il codice sorgente OpenJDK per i metodi floatValue()e doubleValue(). Entrambi sembrano ripiegare su Float.parseFloat()e Double.parseDouble(), rispettivamente, che possono restituire l'infinito positivo o negativo. Ad esempio, l'analisi di una stringa di 10.000 9s restituirà l'infinito positivo. A quanto ho capito, BigDecimalnon ha un concetto interno di infinito. Inoltre, analizzando una stringa di 100 9s come double1.0E100, che non è infinito, ma perde precisione.

Che cos'è un'implementazione ragionevole floatValueExact()e doubleValueExact()?

Ho pensato a una doublesoluzione combinando BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)e Double.toString(double), ma sembra disordinato. Voglio chiedere qui perché potrebbe (deve!) Esserci una soluzione più semplice.

Per essere chiari, non ho bisogno di una soluzione ad alte prestazioni.


7
Immagino che potresti trasformarti in doppio, quindi riconvertire il doppio in BigDecimal e vedere se ottieni lo stesso valore. Non sono sicuro di quale sia il caso d'uso. Se usi i doppi, accetti già di avere valori non esatti. Se vuoi valori esatti, rimani con BigDecimal.
JB Nizet,

Risposte:


6

Dalla lettura dei documenti , tutto ciò che fa con le numTypeValueExactvarianti è verificare l'esistenza di una parte di frazione o se il valore è troppo grande per il tipo numerico e generare eccezioni.

Per quanto riguarda floatValue()e doubleValue(), viene eseguito un controllo di overflow simile, ma invece di generare un'eccezione, restituisce invece Double.POSITIVE_INFINITYo Double.NEGATIVE_INFINITYper i doppi e Float.POSITIVE_INFINITYo Float.NEGATIVE_INFINITYper i float.

Pertanto l'implementazione più ragionevole (e più semplice) dei exactmetodi per float e double, dovrebbe semplicemente verificare se la conversione ritorna POSITIVE_INFINITYo NEGATIVE_INFINITY.


Inoltre , ricorda che è BigDecimalstato progettato per gestire la mancanza di precisione derivante dall'uso floato doubleper grandi irrazionali, quindi come ha commentato @JB Nizet , un altro controllo che puoi aggiungere a quello sopra sarebbe convertire doubleo floattornare indietro BigDecimalper vedere se hai ancora lo stesso valore. Ciò dovrebbe dimostrare che la conversione era corretta.

Ecco come sarebbe un metodo simile floatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!Float.isInfinite(result)) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

L'uso di al compareToposto di equalssopra è intenzionale in modo da non diventare troppo rigoroso con i controlli. equalsvaluterà vero solo quando i due BigDecimaloggetti hanno lo stesso valore e scala (dimensione della parte della frazione del decimale), mentre compareTotrascurerà questa differenza quando non ha importanza. Per esempio 2.0vs 2.00.


Molto furbo. Esattamente quello di cui avevo bisogno! Nota: è possibile utilizzare anche Float.isFinite()o Float.isInfinite(), ma questo è facoltativo. :)
kevinarpe,

3
Dovresti anche preferire compareTo piuttosto che uguale a, perché equals () in BigDecimal considererà 2.0 e 2.00 come valori diversi.
JB Nizet,

I valori che non possono essere rappresentati con precisione come float non funzioneranno. Esempio: 123.456f. Immagino che ciò sia dovuto alle diverse dimensioni del significato (mantissa) tra float a 32 bit e double a 64 bit. Nel codice precedente, ottengo risultati migliori con: if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {. È un cambiamento ragionevole ... o mi manca un altro caso angolare di valori in virgola mobile?
kevinarpe,

1
@kevinarpe - 123.456fconcettualmente è lo stesso del tuo esempio 1.0E100. Mi sembra che se hai bisogno di verificare che la conversione da BigDecimal -> in virgola mobile binaria sia esatta, allora quel problema è necessario ; cioè la conversione non dovrebbe essere contemplata.
Stephen C,
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.