Pregunta

Actualmente tengo este método:

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

Pero este método falla para checkDecmialPlaces (649632196443.4279, 4) probablemente porque hago matemáticas de base 10 en un número de base 2.

Entonces, ¿cómo se puede hacer esta verificación correctamente?

Pensé en obtener una representación de cadena del valor doble y luego comprobarlo con una expresión regular, pero eso me pareció extraño.

EDITAR: Gracias por todas las respuestas. Hay casos en los que realmente obtengo un doble y para esos casos implementé lo siguiente:

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

Cambié el round a un truncamiento. Parece que el cálculo realizado en round aumenta demasiado la imprecisión. Al menos en el caso de prueba que falla.
Como algunos de ustedes señalaron que si pudiera llegar a la entrada de cadena 'real', debería usar BigDecimal para verificar y así lo he hecho:

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

El valor double que obtengo proviene de la API de Apache POI que lee archivos de Excel. Hice algunas pruebas y descubrí que, aunque la API devuelve valores double para celdas numéricas, puedo obtener una representación precisa cuando formateo inmediatamente ese double con el 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);

Esto también funciona para valores que no se pueden representar exactamente en formato binario.

¿Fue útil?

Solución

La prueba falla, porque ha alcanzado la precisión de la representación de punto flotante binario, que es de aproximadamente 16 dígitos con IEEE754 doble precisión . Multiplicar por 649632196443.4279 por 10000 truncará la representación binaria, lo que provocará errores al redondear y dividir después, invalidando por completo el resultado de su función.

Para obtener más detalles, consulte http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Una mejor manera sería verificar si los decimales n + 1 están por debajo de cierto umbral. Si d - round (d) es menor que epsilon (consulte limit ), la representación decimal de d no tiene decimales significativos. De manera similar, si (d - round (d)) * 10 ^ n es menor que epsilon , d puede tener como máximo n lugares significativos.

Utilice Jon Skeet 's DoubleConverter para verificar los casos en que d no es lo suficientemente preciso como para contener los decimales están buscando.

Otros consejos

Si su objetivo es representar un número con exactamente n cifras significativas a la derecha del decimal, BigDecimal es la clase a utilizar.

  

Inmutable, firmada con precisión arbitraria   numeros decimales. Un BigDecimal consiste   de un entero de precisión arbitraria   valor sin escala y un entero de 32 bits   escala. Si es cero o positivo, la escala   es el número de dígitos a la derecha   del punto decimal. Si es negativo, el   el valor sin escala del número es   multiplicado por diez para el poder de la   negación de la escala. El valor de   el número representado por el   BigDecimal es por lo tanto (unscaledValue   & # 215; Escala 10).

scale se puede configurar a través de setScale (int)

Como con toda la aritmética de coma flotante, no debe verificar la igualdad, sino que el error (epsilon) es lo suficientemente pequeño.

Si reemplaza:

return (d==check);

con algo como

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

debería funcionar. Obviamente, el épsilon debe seleccionarse para que sea lo suficientemente pequeño en comparación con el número de decimales que está buscando.

El tipo double es un número binario de coma flotante. Siempre hay imprecisiones aparentes al tratar con ellos como si fueran números decimales de coma flotante. No sé si alguna vez podrás escribir tu función para que funcione de la manera que quieras.

Probablemente tendrá que volver a la fuente original del número (quizás una entrada de cadena) y mantener la representación decimal si es importante para usted.

Si puedes cambiar a BigDecimal, entonces, como explica Ken G, eso es lo que deberías usar.

Si no, entonces tiene que lidiar con una serie de problemas como se menciona en las otras respuestas. Para mí, se trata de un número binario (doble) y hace una pregunta sobre una representación decimal de ese número; es decir, está preguntando acerca de una cadena. Creo que tu intuición es correcta.

No estoy seguro de que esto sea realmente factible en general. Por ejemplo, ¿cuántos lugares decimales tiene 1.0e-13 ? ¿Qué pasa si resultó de algún error de redondeo al hacer aritmética y realmente solo está 0 disfrazado? Si está activado, por otro lado, está preguntando si hay dígitos distintos de cero en los primeros n lugares decimales, puede hacer algo como:

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

Creo que esto es mejor Convertir a cadena e interrogar el valor para el exponente

 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);
 }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top