Domanda

Attualmente ho questo metodo:

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);      
}

Ma questo metodo ha esito negativo per checkDecmialPlaces (649632196443.4279, 4) probabilmente perché eseguo la matematica di base 10 su un numero di base 2

Quindi come si può fare questo controllo correttamente?

Ho pensato di ottenere una rappresentazione in forma di stringa del doppio valore e quindi verificarlo con una regexp, ma mi è sembrato strano.

Modifica Grazie per tutte le risposte Ci sono casi in cui ottengo davvero un doppio e per quei casi ho implementato quanto segue:

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;
}

Ho cambiato il round in un troncamento. Sembra che il calcolo fatto in round aumenti troppo l'imprecisione. Almeno nel testcase fallito.
Come alcuni di voi hanno sottolineato se potessi arrivare all'input di stringa "reale", dovrei usare BigDecimal per verificare e quindi ho fatto:

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

Il valore double che ottengo proviene dall'API POI di Apache che legge i file Excel. Ho fatto alcuni test e ho scoperto che sebbene l'API restituisca i valori double per le celle numeriche, posso ottenere una rappresentazione accurata quando formatto immediatamente quel double con 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);

Funziona anche con valori che non possono essere rappresentati esattamente in formato binario.

È stato utile?

Soluzione

Il test ha esito negativo, poiché è stata raggiunta la precisione della rappresentazione binaria in virgola mobile, che è di circa 16 cifre con IEEE754 doppia precisione . Moltiplicare per 649632196443.4279 per 10000 troncerà la rappresentazione binaria, causando errori durante l'arrotondamento e la divisione in seguito, invalidando così completamente il risultato della tua funzione.

Per maggiori dettagli vedi http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Un modo migliore sarebbe verificare se le posizioni decimali n + 1 sono al di sotto di una determinata soglia. Se d - round (d) è inferiore a epsilon (vedi limit ), la rappresentazione decimale di d non ha posizioni decimali significative. Allo stesso modo se (d - round (d)) * 10 ^ n è inferiore a epsilon , d può avere al massimo n posizioni significative.

Usa Jon Skeet di DoubleConverter per verificare i casi in cui d non è abbastanza preciso da contenere i decimali stai cercando.

Altri suggerimenti

Se il tuo obiettivo è rappresentare un numero con esattamente n cifre significative a destra del decimale, BigDecimal è la classe da utilizzare.

  

Immutabile, precisione arbitraria firmata   numeri decimali. Un BigDecimal è costituito   di un numero intero di precisione arbitraria   valore non scalato e un numero intero a 32 bit   scala. Se zero o positivo, la scala   è il numero di cifre a destra   del punto decimale. Se negativo, il   il valore non scalato del numero è   moltiplicato per dieci al potere del   negazione della scala. Il valore di   il numero rappresentato dal   BigDecimal è quindi (unscaledValue   & # 215; 10-scala).

scale può essere impostato tramite setScale (int)

Come per tutta l'aritmetica in virgola mobile, non si dovrebbe verificare l'uguaglianza, ma piuttosto che l'errore (epsilon) è sufficientemente piccolo.

Se si sostituisce:

return (d==check);

con qualcosa di simile

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

dovrebbe funzionare. Ovviamente, epsilon dovrebbe essere selezionato per essere abbastanza piccolo rispetto al numero di decimali che stai controllando.

Il tipo double è un numero binario in virgola mobile. Ci sono sempre apparenti inesattezze nel trattarli come se fossero numeri decimali in virgola mobile. Non so che sarai mai in grado di scrivere la tua funzione in modo che funzioni nel modo desiderato.

Probabilmente dovrai tornare alla fonte originale del numero (forse una stringa di input) e mantenere la rappresentazione decimale se è importante per te.

Se puoi passare a BigDecimal, come spiega Ken G, è quello che dovresti usare.

In caso contrario, devi affrontare una serie di problemi, come indicato nelle altre risposte. Per me, hai a che fare con un numero binario (doppio) e fai una domanda su una rappresentazione decimale di quel numero; cioè, stai chiedendo una stringa. Penso che la tua intuizione sia corretta.

Non sono sicuro che questo sia davvero fattibile in generale. Ad esempio, quante posizioni decimali ha 1.0e-13 ? E se risultasse da un errore di arrotondamento mentre si fa l'aritmetica e in realtà è solo 0 sotto mentite spoglie? Se questa opzione è attivata, d'altra parte ti viene chiesto se ci sono cifre diverse da zero nelle prime n posizioni decimali che puoi fare come:

   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;
   }

Penso che sia meglio Converti in stringa e interroga il valore per l'esponente

 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);
 }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top