Comment éviter les erreurs de précision floating point avec des flotteurs ou des doubles en Java?

j'ai un problème très ennuyeux avec de longues sommes de flotteurs ou doubles en Java. Fondamentalement, l'idée est que si j'exécute:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )
    System.out.println( value );

ce que j'obtiens c'est:

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001

je comprends qu'il y a une accumulation de l'erreur de précision flottante, cependant, comment se débarrasser de cela? J'ai essayé d'utiliser des doubles à la moitié de l'erreur, mais le résultat est toujours le même.

des idées?

29
demandé sur TylerH 2011-03-10 11:35:17

12 réponses

il n'y a pas de représentation exacte de 0.1 comme float ou double . En raison de cette représentation d'erreur les résultats sont légèrement différents de ce que vous attendiez.

quelques approches que vous pouvez utiliser:

  • lorsque vous utilisez le type double , n'affichez que le nombre de chiffres dont vous avez besoin. Lors de la vérification de l'égalité permettre une petite tolérance de toute façon.
  • utiliser alternativement un type qui permet de mémoriser les numéros que vous essayez de représenter exactement, par exemple BigDecimal peut représenter de 0,1 exactement.

exemple de code pour BigDecimal :

BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
     value.compareTo(BigDecimal.ONE) < 0;
     value = value.add(step)) {
    System.out.println(value);
}

voir en ligne: ideone

32
répondu Mark Byers 2011-03-10 08:46:42

vous pouvez éviter ce problème spécifique en utilisant des classes comme BigDecimal . float et double , étant IEEE 754 flottant-point, ne sont pas conçus pour être parfaitement précis, ils sont conçus pour être rapide. Mais notez le point de Jon ci-dessous: BigDecimal ne peut pas représenter" un tiers "avec précision, pas plus que double peut représenter" un dixième " avec précision. Mais pour (disons) les calculs financiers, BigDecimal et les classes comme elle ont tendance à être la voie à suivre, parce qu'ils peuvent représenter les nombres de la façon dont nous les humains avons tendance à penser à eux.

9
répondu T.J. Crowder 2011-03-10 08:44:19

N'utilisez pas float/double dans un itérateur car cela maximise votre erreur d'arrondi. Si vous utilisez juste le

for (int i = 0; i < 10; i++)
    System.out.println(i / 10.0);

it imprime

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

Je sais que BigDecimal est un choix populaire, mais je préfère double pas parce que son beaucoup plus rapide mais son généralement beaucoup plus court/propre à comprendre.

si vous comptez le nombre de symboles comme mesure de la complexité du code

  • à l'aide de double = > 11 symboles
  • utilisez BigDecimal (de l'exemple de @Mark Byers) = > 21 symboles

BTW: ne pas utiliser des float, sauf s'il existe un vraiment de bonnes raisons de ne pas utiliser de double.

7
répondu Peter Lawrey 2011-03-10 09:20:11

Il est pas juste une erreur accumulée (et n'a absolument rien à voir avec Java). 1.0f , une fois traduit en code réel, n'a pas la valeur 0.1 - vous obtenez déjà une erreur d'arrondissement.

À Partir De La Virgule Flottante Guide:

Que puis-je faire pour éviter ce problème?

cela dépend de quel type de les calculs que vous faites.

  • si vous avez vraiment besoin de vos résultats pour additionner exactement, surtout lorsque vous travaillez avec de l'argent: utilisez un type de données décimal spécial.
  • si vous ne voulez tout simplement pas voir toutes ces places décimales supplémentaires: formatez simplement votre résultat arrondi à un fixe nombre de décimales lorsque affichant.
  • si vous n'avez pas de type de données décimal disponible, une alternative est de travailler avec des nombres entiers, p.ex. faire de l'argent les calculs entièrement en cent. Mais c'est plus de travail et a quelques inconvénient.

lire le lien-vers le site pour des informations détaillées.

3
répondu Michael Borgwardt 2011-03-10 08:40:59

par souci d'exhaustivité, je recommande celui-ci:

Shewchuck, "Robust Adaptive Floating-Point Geometric Predicates", si vous voulez plus d'exemples de la façon d'effectuer l'arithmétique exacte avec la virgule flottante - ou au moins la précision contrôlée qui est l'intention originale de l'auteur, http://www.cs.berkeley.edu/~JRS / papers / robust.pdf

3
répondu aka.nice 2012-11-16 20:28:33

une autre solution est de renoncer à == et de vérifier si les deux valeurs sont assez proche . (Je sais que ce n'est pas ce que vous avez demandé dans le corps, mais je vais répondre à la question titre.)

2
répondu aib 2011-03-10 08:45:59

j'avais fait face au même problème, résolu le même en utilisant BigDecimal. Ci-dessous l'extrait qui m'a aidé.

double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
    total += (double)array[i];
    bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);

J'espère que ça vous aidera.

2
répondu Balu SKT 2015-07-24 06:39:23

vous devez utiliser un type de données décimal, pas flotteurs:

https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

2
répondu Tobias Schittkowski 2016-09-08 08:59:16
package loopinamdar;

import java.text.DecimalFormat;

public class loopinam {
    static DecimalFormat valueFormat = new DecimalFormat("0.0");

    public static void main(String[] args) {
        for (float value = 0.0f; value < 1.0f; value += 0.1f)
            System.out.println("" + valueFormat.format(value));
    }
}
2
répondu Solus 2017-02-24 09:04:04

faire D'abord un double . N'utilisez jamais float ou vous aurez de la difficulté à utiliser les utilitaires java.lang.Math .

maintenant, si vous vous trouvez à connaître à l'avance la précision vous voulez et il est égal ou inférieur à 15, alors il devient facile de dire à votre double de se comporter. Vérifier ci-dessous:

// the magic method:
public final static double makePrecise(double value, int precision) {
    double pow = Math.pow(10, precision);
    long powValue = Math.round(pow * value);
    return powValue / pow;
}

maintenant, chaque fois que vous opérez, vous doit indiquer votre double résultat pour se comporter:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 1) + " => " + value );

sortie:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999

si vous avez besoin de plus de 15 précision, alors vous n'avez pas de chance:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 16) + " => " + value );

sortie:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999

NOTE1: pour la performance, vous devez mettre en cache l'opération Math.pow dans un tableau. Pas fait ici pour plus de clarté.

NOTE2: C'est pourquoi nous n'utilisons jamais double s pour les prix, mais long s où le dernier N (i.e. Où N <= 15, habituellement 8) chiffres sont les chiffres décimaux. Alors vous pouvez oublier ce que j'ai écrit ci-dessus:)

1
répondu rdalmeida 2018-04-14 14:55:16

si vous voulez continuer à utiliser float et éviter d'accumuler des erreurs en ajoutant à plusieurs reprises 0.1f , essayez quelque chose comme ceci:

for (int count = 0; count < 10; count++) {
    float value = 0.1f * count;
    System.out.println(value);
}

notez cependant, comme d'autres l'ont déjà expliqué, que float n'est pas un type de données infiniment précis.

0
répondu Jesper 2011-03-10 09:18:35

vous avez juste besoin d'être conscient de la précision requise dans votre calcul et la précision que le type de données choisi est capable de et de présenter vos réponses en conséquence.

par exemple, si vous avez affaire à des nombres avec 3 chiffres significatifs, l'utilisation de float (qui fournit une précision de 7 chiffres significatifs) est appropriée. Cependant, vous ne pouvez pas citer votre réponse finale avec une précision de 7 chiffres significatifs si vos valeurs de départ n'ont qu'une précision de 2 chiffres significatifs.

5.01 + 4.02 = 9.03 (to 3 significant figures)

dans votre exemple vous effectuez des additions multiples, et avec chaque addition il y a un impact conséquent sur la précision finale.

0
répondu Nick Pierpoint 2011-03-10 10:10:27