J'ai un problème très ennuyeux avec de longues sommes de flotteurs ou de 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 je reçois 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 s'en débarrasser? J'ai essayé d'utiliser le double de la moitié de l'erreur, mais le résultat est toujours le même.
Des idées?
Il n'y a pas de représentation exacte de 0,1 comme float
ou double
. En raison de cette erreur de représentation, les résultats sont légèrement différents de ce que vous attendiez.
Quelques approches que vous pouvez utiliser:
double
, n'affichez que le nombre de chiffres dont vous avez besoin. Lors de la vérification de l'égalité, autorisez une petite tolérance dans les deux cas.BigDecimal
peut représenter 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
Vous pouvez éviter ce problème spécifique en utilisant des classes comme BigDecimal
. float
et double
, étant à virgule flottante IEEE 754, ne sont pas conçus pour être parfaitement précis, ils sont conçus pour être rapides. Mais notez le point de Jon ci-dessous: BigDecimal
ne peut pas représenter "un tiers" avec précision, pas plus que double
ne peut représenter avec précision "un dixième". Mais pour (disons) les calculs financiers, BigDecimal
et des classes comme celle-ci ont tendance à être la voie à suivre, car ils peuvent représenter des nombres de la façon dont nous, les humains, avons tendance à penser à eux.
N'utilisez pas float/double dans un itérateur car cela maximise votre erreur d'arrondi. Si vous utilisez simplement les éléments suivants
for (int i = 0; i < 10; i++)
System.out.println(i / 10.0);
il 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 doubler non pas parce que c'est beaucoup plus rapide mais c'est généralement beaucoup plus court/plus propre à comprendre.
Si vous comptez le nombre de symboles comme mesure de la complexité du code
BTW: n'utilisez pas float sauf s'il y a vraiment une bonne raison de ne pas utiliser double.
Ce n'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'arrondi.
De Le Guide à virgule flottante:
Que puis-je faire pour éviter ce problème?
Cela dépend du type de calculs que vous effectuez.
- Si vous avez vraiment besoin que vos résultats s'additionnent 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 décimales supplémentaires: formatez simplement votre résultat arrondi à un nombre fixe de décimales lors de son affichage.
- Si vous n'avez pas de type de données décimal disponible, une alternative est de travailler avec des entiers, par ex. faire des calculs d'argent entièrement en cents. Mais cela demande plus de travail et présente certains inconvénients.
Lisez le site lié pour des informations détaillées.
Par souci d'exhaustivité, je recommande celui-ci:
Shewchuck, "Robust Adaptive Floating-Point Geometric Predicates", si vous voulez plus d'exemples sur la façon d'effectuer une arithmétique exacte avec virgule flottante - ou au moins une précision contrôlée qui est l'intention originale de l'auteur, http: // www. cs.berkeley.edu/~jrs/papers/robustr.pdf
Une autre solution consiste à renoncer à ==
et vérifiez si les deux valeurs sont assez proches. (Je sais que ce n'est pas ce que vous avez demandé dans le corps, mais je réponds au titre de la question.)
J'avais rencontré le même problème, résolu le même en utilisant BigDecimal. Voici l'extrait de code 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 cela vous aidera.
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));
}
}
Vous devez utiliser un type de données décimal et non flottant:
https://docs.Oracle.com/javase/7/docs/api/Java/math/BigDecimal.html
Faites-en d'abord un double. N'utilisez jamais float ou vous aurez du mal à utiliser le Java.lang.Math
utilitaires.
Maintenant, si vous connaissez à l'avance la précision que vous voulez et qu'elle est égale ou inférieure à 15, alors il devient facile de dire à votre double s se comporter. Vérifiez 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 effectuez une opération, vous devez indiquer à votre double résultat de se comporter:
for ( double value = 0.0d; value < 1.0d; value += 0.1d )
System.out.println( makePrecise(value, 1) + " => " + value );
Production:
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, vous n'avez pas de chance:
for ( double value = 0.0d; value < 1.0d; value += 0.1d )
System.out.println( makePrecise(value, 16) + " => " + value );
Production:
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 les performances, vous devez mettre en cache le Math.pow
opération 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ù les N derniers (c'est-à-dire où N <= 15, généralement 8) chiffres sont les chiffres décimaux. Ensuite, vous pouvez oublier ce que j'ai écrit ci-dessus :)
Si vous souhaitez 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.
Il vous suffit d'être conscient de la précision requise dans votre calcul et de la précision dont le type de données choisi est capable 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 plusieurs ajouts et, avec chaque ajout, il y a un impact conséquent sur la précision finale.