Question

Actuellement, j'ai cette méthode:

static boolean checkDecimalPlaces(double d, int decimalPlaces){
    if (d==0) return true;

    double multiplier = Math.pow(10, decimalPlaces); 
    double check  =  d * multiplier;
    check = Math.round(check);      
    check = check/multiplier; 
    return (d==check);      
}

Mais cette méthode échoue pour checkDecmialPlaces (649632196443.4279, 4) probablement parce que je fais des calculs en base 10 sur un nombre en base 2.

Alors, comment faire cette vérification correctement?

Je pensais obtenir une représentation sous forme de chaîne de la valeur double, puis vérifier cela avec une expression rationnelle - mais cela me paraissait bizarre.

MODIFIER: Merci pour toutes les réponses. Il y a des cas où je reçois vraiment un double et dans ces cas, j'ai mis en œuvre ce qui suit:

private static boolean checkDecimalPlaces(double d, int decimalPlaces) {
    if (d == 0) return true;

    final double epsilon = Math.pow(10.0, ((decimalPlaces + 1) * -1));

    double multiplier = Math.pow(10, decimalPlaces);
    double check = d * multiplier;
    long checkLong = (long) Math.abs(check);
    check = checkLong / multiplier;

    double e = Math.abs(d - check);
    return e < epsilon;
}

J'ai changé le round en une troncature. Il semble que le calcul effectué dans round augmente trop l'imprécision. Au moins dans le test qui a échoué.
Comme certains d'entre vous l'ont fait remarquer, si je pouvais accéder à la "vraie" entrée de chaîne, je devrais utiliser BigDecimal pour vérifier et c'est ce que j'ai fait:

BigDecimal decimal = new BigDecimal(value);
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces);
return checkDecimal.scale() == 0;

La valeur double que je récupère provient de l'API Apache POI qui lit les fichiers Excel. J'ai fait quelques tests et découvert que, bien que l'API renvoie des valeurs double pour les cellules numériques, je peux obtenir une représentation précise lorsque je formate immédiatement ce double avec DecimalFormat. :

DecimalFormat decimalFormat = new DecimalFormat();
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE);
// don't use grouping for numeric-type cells
decimalFormat.setGroupingUsed(false);
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
value = decimalFormat.format(numericValue);

Ceci fonctionne également pour les valeurs qui ne peuvent pas être représentées exactement au format binaire.

Était-ce utile?

La solution

Le test échoue car vous avez atteint l'exactitude de la représentation binaire en virgule flottante, qui est d'environ 16 chiffres avec IEEE754 double précision . Multiplier par 649632196443.4279 par 10000 tronquera la représentation binaire, ce qui entraînera des erreurs lors des arrondis et des divisions ultérieurs, ce qui invalidera complètement le résultat de votre fonction.

Pour plus de détails, voir http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Une meilleure solution consiste à vérifier si les décimales n + 1 sont inférieures à un certain seuil. Si d - arrondi (d) est inférieur à epsilon (voir limite ), la représentation décimale de d n'a pas de décimale significative. De même, si (d - round (d)) * 10 ^ n est inférieur à epsilon , d peut avoir au plus n endroits significatifs.

Utilisez les Jon Skeet DoubleConverter pour rechercher les cas où d n'est pas assez précis pour conserver les décimales recherchons.

Autres conseils

Si votre objectif est de représenter un nombre avec exactement n chiffres significatifs à droite du point décimal, BigDecimal est la classe à utiliser.

  

Immuable, à précision arbitraire, signé   Nombres décimaux. Un BigDecimal consiste   d'un entier de précision arbitraire   valeur non mise à l'échelle et un entier de 32 bits   échelle. Si zéro ou positif, l'échelle   est le nombre de chiffres à droite   de la virgule décimale. Si négatif, le   valeur non mise à l'échelle du nombre est   multiplié par dix à la puissance de la   négation de la balance. La valeur de   le nombre représenté par le   BigDecimal est donc (unscaledValue   & # 215; Échelle 10).

scale peut être défini via setScale (int)

Comme pour toute arithmétique en virgule flottante, vous ne devez pas vérifier l’égalité, mais plutôt que l’erreur (epsilon) est suffisamment petite.

Si vous remplacez:

return (d==check);

avec quelque chose comme

return (Math.abs(d-check) <= 0.0000001);

ça devrait marcher. Évidemment, epsilon doit être choisi pour être suffisamment petit par rapport au nombre de décimales que vous recherchez.

Le type double est un nombre à virgule flottante binaire. Il y a toujours des inexactitudes apparentes quand on les traite comme s'il s'agissait de nombres décimaux à virgule flottante. Je ne sais pas si vous pourrez écrire votre fonction pour qu'elle fonctionne comme vous le souhaitez.

Vous devrez probablement revenir à la source d'origine du nombre (une entrée de chaîne peut-être) et conserver la représentation décimale si elle est importante pour vous.

Si vous pouvez passer à BigDecimal, comme l'explique Ken G, c'est ce que vous devriez utiliser.

Sinon, vous devez faire face à une foule de problèmes, comme indiqué dans les autres réponses. Pour moi, vous avez affaire à un nombre binaire (double) et vous posez une question sur une représentation décimale de ce nombre; c'est-à-dire que vous vous interrogez sur une chaîne. Je pense que votre intuition est correcte.

Je ne suis pas sûr que ce soit vraiment faisable en général. Par exemple, combien de décimales 1.0e-13 a-t-il? Que se passe-t-il si cela résulte d'une erreur d'arrondi lors du calcul, et si le code est vraiment 0 déguisé? Si cette option est activée, vous demandez s'il existe des chiffres non nuls dans les premières n décimales pour lesquelles vous pouvez faire quelque chose comme:

   static boolean checkDecimalPlaces(double d, unsigned int decimalPlaces){
      // take advantage of truncation, may need to use BigInt here
      // depending on your range
      double d_abs = Math.abs(d);
      unsigned long d_i = d_abs; 
      unsigned long e = (d_abs - d_i) * Math.pow(10, decimalPlaces);
      return e > 0;
   }

Je pense que c'est mieux Convertir en chaîne et interroger la valeur de l'exposant

 public int calcBase10Exponet (Number increment)
 {
  //toSting of 0.0=0.0
  //toSting of 1.0=1.0
  //toSting of 10.0=10.0
  //toSting of 100.0=100.0
  //toSting of 1000.0=1000.0
  //toSting of 10000.0=10000.0
  //toSting of 100000.0=100000.0
  //toSting of 1000000.0=1000000.0
  //toSting of 1.0E7=1.0E7
  //toSting of 1.0E8=1.0E8
  //toSting of 1.0E9=1.0E9
  //toSting of 1.0E10=1.0E10
  //toSting of 1.0E11=1.0E11
  //toSting of 0.1=0.1
  //toSting of 0.01=0.01
  //toSting of 0.0010=0.0010  <== need to trim off this extra zero
  //toSting of 1.0E-4=1.0E-4
  //toSting of 1.0E-5=1.0E-5
  //toSting of 1.0E-6=1.0E-6
  //toSting of 1.0E-7=1.0E-7
  //toSting of 1.0E-8=1.0E-8
  //toSting of 1.0E-9=1.0E-9
  //toSting of 1.0E-10=1.0E-10
  //toSting of 1.0E-11=1.0E-11
  double dbl = increment.doubleValue ();
  String str = Double.toString (dbl);
//  System.out.println ("NumberBoxDefaultPatternCalculator: toSting of " + dbl + "=" + str);
  if (str.contains ("E"))
  {
   return Integer.parseInt (str.substring (str.indexOf ("E") + 1));
  }
  if (str.endsWith (".0"))
  {
   return str.length () - 3;
  }
  while (str.endsWith ("0"))
  {
   str = str.substring (0, str.length () - 1);
  }
  return - (str.length () - str.indexOf (".") - 1);
 }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top