Conserver la précision avec double en Java
public class doublePrecision {
public static void main(String[] args) {
double total = 0;
total += 5.6;
total += 5.8;
System.out.println(total);
}
}
le code ci-dessus imprime:
11.399999999999
Comment puis-je obtenir que ce soit imprimé (ou que je puisse l'utiliser comme) 11.4?
20 réponses
comme d'autres l'ont mentionné, vous voudrez probablement utiliser la classe BigDecimal
, si vous voulez avoir une représentation exacte de 11.4.
maintenant, une petite explication sur la raison de ce qui se passe:
les types primitifs float
et double
en Java sont des nombres à virgule flottante , où le nombre est stocké comme une représentation binaire d'une fraction et d'un exposant.
plus précisément, une valeur à virgule flottante de double précision telle que double
est une valeur à 64 bits, où:
- 1 bit indique le signe (positif ou négatif).
- 11 bits pour l'exposant.
- 52 bits pour les chiffres significatifs (la partie fractionnaire en binaire).
ces parties sont combinées pour produire une représentation double
de valeur.
(Source: Wikipédia: Double précision )
pour une description détaillée de la façon dont les valeurs à virgule flottante sont gérées en Java, voir la Section 4.2.3: types, Formats et valeurs à virgule flottante de la spécification de langage Java.
le byte
, char
, int
, long
les types sont les numéros à point fixe , qui sont des représentations exactes des nombres. Contrairement aux nombres à points fixes, les nombres à points flottants ne seront pas toujours en mesure de retourner une représentation exacte d'un nombre. C'est la raison pour laquelle vous finissez avec 11.399999999999
comme le résultat de 5.6 + 5.8
.
si vous avez besoin d'une valeur qui est exacte, comme 1.5 ou 150.1005, vous voudrez utiliser un des types de point fixe, qui sera en mesure de représenter le nombre exactement.
comme cela a été mentionné plusieurs fois déjà, Java a une classe BigDecimal
qui traitera de très grands nombres et de très petits nombres.
de la référence de L'API Java pour la classe BigDecimal
:
immuable, précision arbitraire décimal signé nombre. Un BigDecimal se compose d'un nombre entier de précision arbitraire non calibré valeur et une échelle entière de 32 bits. Si zéro ou positif, l'échelle est nombre de chiffres à droite de la virgule. Si le résultat est négatif, le la valeur non scalée du nombre est multiplié par dix au pouvoir du négation de la balance. La valeur de le nombre représenté par la BigDecimal est donc (unscaledValue × 10^-échelle).
il y a eu de nombreuses questions sur le débordement de la pile concernant la question des nombres à virgule flottante et sa précision. Voici une liste de questions qui peuvent être d'intérêt:
- Pourquoi vois-je une double variable initialisée à une certaine valeur comme 21.4 comme 21.39999999618530273?
- Comment imprimer vraiment de gros chiffres en C++
- Comment est stocké floating point? Quand est-il de la matière?
- utiliser flottant ou décimal pour application comptable montant en dollars?
si vous voulez vraiment descendre aux détails graveleux des nombres à virgule flottante, jetez un oeil à ce que tout informaticien devrait savoir au sujet de L'arithmétique à virgule flottante .
lorsque vous entrez un nombre double, par exemple, 33.33333333333333
, la valeur que vous obtenez est en fait la valeur de double précision représentative la plus proche, qui est exactement:
33.3333333333333285963817615993320941925048828125
divisé par 100 donne:
0.333333333333333285963817615993320941925048828125
qui n'est pas non plus représentatif en tant que nombre à double précision, donc encore une fois il est arrondi à la valeur représentative la plus proche, qui est exactement:
0.3333333333333332593184650249895639717578887939453125
lorsque vous imprimez cette valeur, il est arrondi encore une fois à 17 chiffres décimaux, donnant:
0.33333333333333326
si vous voulez simplement transformer des valeurs en fractions, vous pouvez créer une classe de Fraction qui contient un champ numérateur et dénominateur.
méthodes d'Écriture pour ajouter, soustraire, multiplier et diviser ainsi qu'un toDouble méthode. De cette façon, vous pouvez éviter les flotteurs pendant les calculs.
EDIT: une mise en œuvre Rapide,
public class Fraction {
private int numerator;
private int denominator;
public Fraction(int n, int d){
numerator = n;
denominator = d;
}
public double toDouble(){
return ((double)numerator)/((double)denominator);
}
public static Fraction add(Fraction a, Fraction b){
if(a.denominator != b.denominator){
double aTop = b.denominator * a.numerator;
double bTop = a.denominator * b.numerator;
return new Fraction(aTop + bTop, a.denominator * b.denominator);
}
else{
return new Fraction(a.numerator + b.numerator, a.denominator);
}
}
public static Fraction divide(Fraction a, Fraction b){
return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}
public static Fraction multiply(Fraction a, Fraction b){
return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}
public static Fraction subtract(Fraction a, Fraction b){
if(a.denominator != b.denominator){
double aTop = b.denominator * a.numerator;
double bTop = a.denominator * b.numerator;
return new Fraction(aTop-bTop, a.denominator*b.denominator);
}
else{
return new Fraction(a.numerator - b.numerator, a.denominator);
}
}
}
observez que vous auriez le même problème si vous aviez utilisé l'arithmétique décimale de précision limitée, et a voulu traiter avec 1/3: 0.3333333 * 3 est 0.9999999, pas 1.00000000.
malheureusement, 5.6, 5.8 et 11.4 ne sont pas des nombres ronds en binaire, parce qu'ils impliquent des cinquièmes. Donc la représentation float d'eux n'est pas exacte, tout comme 0.3333 n'est pas exactement 1/3.
si tous les nombres que vous utilisez sont des décimales non récurrentes, et que vous voulez des décimales exactes résultats, utilisez BigDecimal. Ou comme d'autres l'ont dit, si vos valeurs sont comme de l'argent dans le sens où elles sont toutes un multiple de 0,01, ou 0,001, ou quelque chose, puis tout multiplier par une puissance fixe de 10 et utiliser int ou long (addition et soustraction sont triviaux: attention à la multiplication).
cependant, si vous êtes satisfait du binaire pour le calcul, mais vous voulez juste imprimer les choses dans un format légèrement plus convivial, essayez java.util.Formatter
ou String.format
. Dans le format chaîne de spécifier une précision inférieure à la précision d'un double. À 10 chiffres significatifs, par exemple, 11.399999999999 est 11.4, de sorte que le résultat sera presque aussi précis et plus lisible par l'homme dans les cas où le résultat binaire est très proche d'une valeur nécessitant seulement quelques décimales.
la précision à spécifier dépend un peu de combien de maths vous avez fait avec vos nombres - en général plus vous faites, plus l'erreur s'accumulera, mais certains algorithmes l'accumulent beaucoup plus rapidement que d'autres (on les appelle "instables" par opposition à "stables" en ce qui concerne les erreurs d'arrondissement). Si tout ce que vous faites est d'ajouter quelques valeurs, alors je suppose que la chute d'une seule décimale de précision va arranger les choses. Expérience.
vous pouvez vouloir regarder en utilisant java.mathématique.Grande classe numérique si vous avez vraiment besoin de mathématiques de précision. Voici un bon article D'Oracle / Sun sur le cas de BigDecimal . Alors que vous ne pouvez jamais représenter 1/3 comme quelqu'un a mentionné, vous peut avoir le pouvoir de décider exactement comment vous voulez que le résultat soit précis. setScale () est votre ami.. :)
Ok, parce que j'ai beaucoup trop de temps sur les mains en ce moment ici un exemple de code qui se rapporte à votre question:
import java.math.BigDecimal;
/**
* Created by a wonderful programmer known as:
* Vincent Stoessel
* xaymaca@gmail.com
* on Mar 17, 2010 at 11:05:16 PM
*/
public class BigUp {
public static void main(String[] args) {
BigDecimal first, second, result ;
first = new BigDecimal("33.33333333333333") ;
second = new BigDecimal("100") ;
result = first.divide(second);
System.out.println("result is " + result);
//will print : result is 0.3333333333333333
}
}
et pour brancher ma nouvelle langue préférée, Groovy, voici un bel exemple de la même chose:
import java.math.BigDecimal
def first = new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")
println "result is " + first/second // will print: result is 0.33333333333333
comme d'autres l'ont noté, toutes les valeurs décimales ne peuvent pas être représentées en binaire puisque la décimale est basée sur les puissances de 10 et binaire est basée sur les puissances de deux.
si la précision compte, utilisez BigDecimal, mais si vous voulez juste une sortie conviviale:
System.out.printf("%.2f\n", total);
vous donnera:
11.40
vous auriez pu en faire un exemple. :)
si vous voulez une précision exacte, utilisez BigDecimal. Sinon, vous pouvez utiliser les ints multipliés par 10 ^ quelle que soit la précision que vous voulez.
vous vous heurtez à la limite de précision du type double.
Java.Les maths ont des facilités d'arithmétique de précision arbitraire.
vous ne pouvez pas, parce que 7.3 n'a pas une représentation finie en binaire. Le plus proche que vous pouvez obtenir est 2054767329987789/2**48 = 7.3+1/1407374883553280.
regardez http://docs.python.org/tutorial/floatingpoint.html pour une explication plus détaillée. (C'est sur le site de Python, mais Java et C++ ont le même "problème".)
la solution dépend de ce que votre problème est exactement:
- si c'est juste que vous n'aimez pas voir tous ces chiffres de bruit, alors fixez votre formatage de chaîne. N'affichez pas plus de 15 chiffres significatifs (ou 7 pour flotteur).
- si c'est que l'inexactitude de vos nombres casse des choses comme" si", alors vous devriez écrire si (abs(x - 7.3) < tolérance) au lieu de si (x = 7.3).
- si vous travaillez avec de l'argent, alors ce que vous voulez probablement vraiment est virgule décimale fixe. Stocker un nombre entier nombre de cents ou quelque soit la plus petite unité de votre monnaie est.
- (très peu probable) Si vous avez besoin de plus de 53 bits significatifs (15-16 chiffres significatifs) de précision, alors utilisez un type de point flottant de haute précision, comme Bigdécimal.
private void getRound() {
// this is very simple and interesting
double a = 5, b = 3, c;
c = a / b;
System.out.println(" round val is " + c);
// round val is : 1.6666666666666667
// if you want to only two precision point with double we
// can use formate option in String
// which takes 2 parameters one is formte specifier which
// shows dicimal places another double value
String s = String.format("%.2f", c);
double val = Double.parseDouble(s);
System.out.println(" val is :" + val);
// now out put will be : val is :1.67
}
utilisez java.mathématique.BigDecimal
Doubles sont des fractions binaires internes, de sorte qu'ils ne peuvent parfois pas représenter fractions décimales à la décimale exacte.
les ordinateurs stockent des nombres en binaire et ne peuvent pas réellement représenter des nombres tels que 33.3333333 ou 100.0 exactement. C'est l'une des choses difficiles à propos de l'utilisation de doubles. Vous devrez juste arrondir la réponse avant de la montrer à un utilisateur. Heureusement dans la plupart des applications, vous n'avez pas besoin que beaucoup de décimales de toute façon.
les nombres à virgule flottante diffèrent des nombres réels en ce que pour n'importe quel nombre à virgule flottante donné il y a un nombre à virgule flottante supérieur suivant. De même que les nombres entiers. Il n'y a pas de nombre entier compris entre 1 et 2.
impossible de représenter 1/3 comme flotteur. Il y a un flotteur au-dessous et il y a un flotteur au-dessus, et il y a une certaine distance entre eux. Et 1/3 est dans cet espace.
Apfloat pour Java les revendications de travailler avec une précision arbitraire flottant les nombres à virgule, mais je n'ai jamais utilisé. Sans doute la peine d'un coup d'oeil. http://www.apfloat.org/apfloat_java /
une question similaire a été posée ici avant Java à virgule flottante de haute précision de la bibliothèque
Doubles sont approximations des nombres décimaux dans votre source Java. Vous voyez la conséquence de l'inadéquation entre le double (qui est une valeur codée binaire) et votre source (qui est codée décimale).
Java produit l'approximation binaire la plus proche. Vous pouvez utiliser le java.texte.DecimalFormat pour afficher une meilleure valeur décimale.
utilisez un BigDecimal. Il vous permet même de spécifier des règles d'arrondissement (comme ROUND_HALF_EVEN, qui minimisera l'erreur statistique en arrondissant au voisin pair si les deux sont la même distance; i.e. à la fois 1,5 et 2,5 rond à 2).
Check out BigDecimal, il gère les problèmes traitant de l'arithmétique des points flottants comme ça.
le nouvel appel ressemblerait à ceci:
term[number].coefficient.add(co);
utiliser setScale() pour définir le nombre de précision de place décimale à utiliser.
pourquoi ne pas utiliser la méthode round() de la classe de mathématiques?
// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4
Si vous n'avez pas d'autre choix que d'utiliser les valeurs doubles, pouvez utiliser le code ci-dessous.
public static double sumDouble(double value1, double value2) {
double sum = 0.0;
String value1Str = Double.toString(value1);
int decimalIndex = value1Str.indexOf(".");
int value1Precision = 0;
if (decimalIndex != -1) {
value1Precision = (value1Str.length() - 1) - decimalIndex;
}
String value2Str = Double.toString(value2);
decimalIndex = value2Str.indexOf(".");
int value2Precision = 0;
if (decimalIndex != -1) {
value2Precision = (value2Str.length() - 1) - decimalIndex;
}
int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
sum = value1 + value2;
String s = String.format("%." + maxPrecision + "f", sum);
sum = Double.parseDouble(s);
return sum;
}
ne gaspillez pas votre efford en utilisant BigDecimal. Dans 99.99999% des cas, vous n'avez pas besoin. le type java double est de nature approximative mais dans presque tous les cas, il est suffisamment précis. Rappelez-vous que vous avez une erreur à 14 chiffres significatifs. C'est vraiment négligeable!
pour obtenir une sortie agréable utiliser:
System.out.printf("%.2f\n", total);