web-dev-qa-db-fra.com

Comment résoudre un problème Java Arrondi Double

On dirait que la soustraction déclenche une sorte de problème et que la valeur résultante est fausse.

double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;

78,75 = 787,5 * 10,0/100d

double netToCompany = targetPremium.doubleValue() - tempCommission;

708,75 = 787,5 - 78,75

double dCommission = request.getPremium().doubleValue() - netToCompany;

877.8499999999999 = 1586,6 - 708,75

La valeur attendue qui en résulte serait de 877,85.

Que faut-il faire pour assurer le bon calcul?

73
Patrick

Pour contrôler la précision de l'arithmétique en virgule flottante, vous devez utiliser Java.math.BigDecimal . Lire La nécessité de BigDecimal par John Zukowski pour plus d'informations.

Étant donné votre exemple, la dernière ligne serait la suivante avec BigDecimal.

import Java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Cela se traduit par la sortie suivante.

877.85 = 1586.6 - 708.75
93
Eric Weilnau

Comme indiqué dans les réponses précédentes, il s’agit d’une conséquence de l’arithmétique en virgule flottante.

Comme l'a suggéré un autre poster, lorsque vous effectuez des calculs numériques, utilisez Java.math.BigDecimal.

Cependant, il est difficile d'utiliser BigDecimal. Lorsque vous convertissez la double valeur en un BigDecimal, vous avez le choix d'utiliser un nouveau constructeur BigDecimal(double) ou la méthode fabrique statique BigDecimal.valueOf(double). Utilisez la méthode fabrique statique.

Le double constructeur convertit la totalité de la précision de double en BigDecimal tandis que la fabrique statique le convertit effectivement en String, puis le convertit en BigDecimal. .

Cela devient pertinent lorsque vous rencontrez ces erreurs d'arrondis subtiles. Un nombre peut afficher 0,55, mais sa valeur interne est '0,5849999999999999996447286321199499070644378662109375'. Si vous utilisiez le constructeur BigDecimal, vous obtiendrez un nombre qui n'est PAS égal à 0,585, tandis que la méthode statique vous attribuerait une valeur égale à 0,585.

 valeur double = 0,585; 
 System.out.println (nouveau BigDecimal (valeur)); 
 System.out.println (BigDecimal.valueOf (valeur)); 

sur mon système donne

 0.5849999999999999996447286321199499070644378662109375 
 0.585 
65
Johann Zacharee

Un autre exemple:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

Utilisez BigDecimal à la place.

MODIFIER:

De plus, je tiens à souligner que ce n’est pas un problème d’arrondi "Java". D'autres langues présentent un comportement similaire (mais pas nécessairement cohérent). Java garantit au moins un comportement cohérent à cet égard.

10
toolkit

Je modifierais l'exemple ci-dessus comme suit:

import Java.math.BigDecimal;

BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

De cette façon, vous évitez les pièges de l'utilisation de chaîne pour commencer. Une autre alternative:

import Java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Je pense que ces options sont meilleures que l’utilisation de doubles. Dans webapps, les chiffres commencent quand même comme des chaînes.

8
dog

Chaque fois que vous effectuez des calculs avec des doubles, cela peut arriver. Ce code vous donnerait 877.85:

double réponse = Math.round (dCommission * 100000)/100000.0;

7
Steve Klabnik

Enregistrez le nombre de centimes plutôt que de dollars, et faites simplement le format en dollars lorsque vous le sortez. De cette façon, vous pouvez utiliser un entier qui ne souffre pas des problèmes de précision.

4
tloach

C'est une question amusante.

L'idée derrière la réponse Timons est de spécifier un epsilon qui représente la plus petite précision qu'un double juridique puisse être. Si vous savez dans votre application que vous n'aurez jamais besoin d'une précision inférieure à 0,00000001, il suggère alors qu'il est suffisant pour obtenir un résultat plus précis très proche de la vérité. Utile dans les applications où ils connaissent leur précision maximale (pour financer par exemple des précisions monétaires, etc.)

Toutefois, le problème fondamental que pose l’arrondi est que, lorsque l’on divise par un facteur pour le redimensionner, on introduit une autre possibilité pour les problèmes de précision. Toute manipulation de doublons peut introduire des problèmes d’imprécision avec une fréquence variable. Surtout si vous essayez d'arrondir à un chiffre très significatif (si vos opérandes sont <0), par exemple si vous exécutez ce qui suit avec du code Timons:

System.out.println(round((1515476.0) * 0.00001) / 0.00001);

Aura pour résultat 1499999.9999999998 où l’objectif ici est d’arrondir aux unités de 500 000 (c’est-à-dire 1500 000)

En fait, le seul moyen de vous assurer que vous avez éliminé l'imprécision consiste à passer par un BigDecimal pour réduire l'échelle. par exemple.

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());

En combinant la stratégie epsilon et la stratégie BigDecimal, vous aurez un contrôle précis sur votre précision. L’idée étant qu’epsilon vous rapproche beaucoup et que BigDecimal élimine toute imprécision causée par une nouvelle mise à l’échelle. L'utilisation de BigDecimal réduira toutefois les performances attendues de votre application.

Il m’a été signalé que la dernière étape consistant à utiliser BigDecimal pour redimensionner l’échelle n’est pas toujours nécessaire pour certains cas d’utilisation lorsque vous pouvez déterminer qu’il n’existe aucune valeur en entrée indiquant que la division finale peut réintroduire une erreur. Actuellement, je ne sais pas comment le déterminer correctement. Si quelqu'un sait comment, je serais ravi de l'entendre.

3
Doug

Voir les réponses à cette question . Ce que vous voyez est essentiellement une conséquence naturelle de l’utilisation de l’arithmétique en virgule flottante.

Vous pouvez choisir une précision arbitraire (chiffres significatifs de vos entrées?) Et arrondir votre résultat à celle obtenue, si vous vous sentez à l'aise de le faire.

3
Adam Bellaire

Mieux encore, utilisez JScience car BigDecimal est assez limité (par exemple, aucune fonction sqrt)

double dCommission = 1586.6 - 708.75;
System.out.println(dCommission);
> 877.8499999999999

Real dCommissionR = Real.valueOf(1586.6 - 708.75);
System.out.println(dCommissionR);
> 877.850000000000
2
Tomasz

Jusqu'à présent, le moyen le plus élégant et le plus efficace de le faire en Java:

double newNum = Math.floor(num * 100 + 0.5) / 100;
2
Roman Kagan
double rounded = Math.rint(toround * 100) / 100;
1
Denis