Comment savoir si un BigDecimal peut exactement se convertir en float ou en double?

10

La classe BigDecimala quelques méthodes utiles pour garantir une conversion sans perte:

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

Cependant, les méthodes floatValueExact()et doubleValueExact()n'existent pas.

J'ai lu le code source d'OpenJDK pour les méthodes floatValue()et doubleValue(). Les deux semblent se replier sur Float.parseFloat()et Double.parseDouble(), respectivement, ce qui peut retourner l'infini positif ou négatif. Par exemple, l'analyse d'une chaîne de 10 000 9 renvoie un infini positif. Si je comprends bien, BigDecimaln'a pas un concept interne de l'infini. En outre, l'analyse d'une chaîne de 100 9 comme doubledonne 1.0E100, ce qui n'est pas l'infini, mais perd en précision.

Qu'est-ce qu'une mise en œuvre raisonnable floatValueExact()et doubleValueExact()?

Je pensais à une doublesolution en combinant BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)et Double.toString(double), mais il semble en désordre. Je veux demander ici car il peut (doit!) Y avoir une solution plus simple.

Pour être clair, je n'ai pas besoin d'une solution performante.

kevinarpe
la source
7
Je suppose que vous pourriez transformer en double, puis retransformer le double en BigDecimal, et voir si vous obtenez la même valeur. Je ne sais pas quel est le cas d'utilisation. Si vous utilisez des doubles, vous acceptez déjà d'avoir des valeurs non exactes. Si vous voulez des valeurs exactes, restez avec BigDecimal.
JB Nizet

Réponses:

6

En lisant les documents , tout ce qu'il fait avec les numTypeValueExactvariantes est de vérifier l'existence d'une partie fraction ou si la valeur est trop grande pour le type numérique et de lever des exceptions.

Comme pour floatValue()et doubleValue(), une vérification de débordement similaire est en cours, mais au lieu de lever une exception, elle renvoie à la place Double.POSITIVE_INFINITYou Double.NEGATIVE_INFINITYpour les doubles et Float.POSITIVE_INFINITYou Float.NEGATIVE_INFINITYpour les flottants.

Par conséquent, l'implémentation la plus raisonnable (et la plus simple) des exactméthodes pour float et double, devrait simplement vérifier si la conversion renvoie POSITIVE_INFINITYou NEGATIVE_INFINITY.


De plus , rappelez-vous que cela a BigDecimalété conçu pour gérer le manque de précision qui vient de l'utilisation floatou doublepour les grands irrationnels, par conséquent, comme l'a commenté @JB Nizet , une autre vérification que vous pouvez ajouter à ce qui précède serait de convertir ou de revenir pour voir si vous obtenez toujours la même valeur. Cela devrait prouver que la conversion était correcte.doublefloatBigDecimal

Voici à quoi ressemblerait une telle méthode 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'utilisation de compareToau lieu de equalsci-dessus est intentionnelle afin de ne pas devenir trop stricte avec les contrôles. equalsn'évaluera la valeur true que lorsque les deux BigDecimalobjets ont la même valeur et la même échelle (taille de la partie fractionnaire de la décimale), tandis que cette valeur compareTosera négligée lorsqu'elle n'aura pas d'importance. Par exemple 2.0vs 2.00.

smac89
la source
Très rusé. Exactement ce dont j'avais besoin! Remarque: Vous pouvez également utiliser Float.isFinite()ou Float.isInfinite(), mais cela est facultatif. :)
kevinarpe
3
Vous devriez également préférer compareTo à égal à, car equals () dans BigDecimal considérera 2.0 et 2.00 comme des valeurs différentes.
JB Nizet
Les valeurs qui ne peuvent pas être représentées avec précision comme float ne fonctionneront pas. Exemple: 123.456f. Je suppose que cela est dû à une taille différente de significande (mantisse) entre float 32 bits et double 64 bits. Dans votre code ci - dessus, pour obtenir de meilleurs résultats avec: if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {. Est-ce un changement raisonnable ... ou manque-t-il un autre cas de coin de valeurs à virgule flottante?
kevinarpe
1
@kevinarpe - 123.456fest conceptuellement le même que votre exemple 1.0E100. Il me semble que si vous devez vérifier que la conversion de BigDecimal -> virgule flottante binaire est exacte, alors ce besoin est le problème; c'est-à-dire que la conversion ne doit pas être envisagée.
Stephen C