2008-11-05 32 views
7

Actualmente tengo este método:¿Cómo verificar si un doble tiene como máximo n lugares decimales?

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 por checkDecmialPlaces(649632196443.4279, 4) probablemente porque hago matemáticas de base 10 en una base numérica 2.

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

Pensé en obtener una representación de cadena del doble valor 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 tener una doble y para aquellos casos que implementan los siguientes:

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

he cambiado 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 han señalado si podía llegar a la entrada 'real' de la cadena que debe utilizar BigDecimal para comprobar y por lo que he hecho:

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

El valor double recibo viene de la API de Apache POI que lee los archivos de Excel. Hice algunas pruebas y descubrió que aunque el API devuelve double valores de celdas numéricas puedo conseguir una representación exacta cuando inmediatamente formato que 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 pueden estar representado exactamente en formato binario.

+0

"correcta" no significa mucho aquí. Sus números decimales aleatorios no tienen representaciones binarias exactas. Está buscando un número decimal que esté "lo suficientemente cerca" para que a las personas les guste la versión decimal del valor binario. ¿Qué tan cerca está lo suficientemente cerca? –

Respuesta

6

La prueba falla, debido a que han llegado a la exactitud de la representación en coma flotante binaria, que tiene aproximadamente 16 dígitos con IEEE754 double precision. Multiplicar por 649632196443.4279 por 10000 truncará la representación binaria, lo que provocará errores al redondear y dividir después, lo que invalidará por completo el resultado de su función.

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

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

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

+0

. También miré el DoubleConverter Jon publicado en su responde y parece hacer casi lo mismo que BigDecimal. Me pregunto por qué eliminó su respuesta. – Turismo

3

Al igual que con todas las aritméticas de punto flotante, no debe verificar la igualdad, sino que el error (epsilon) es suficientemente pequeño.

Si reemplaza:

return (d==check); 

con algo como

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

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

+0

El mayor problema es el desbordamiento cuando lo haces: check = d * multiplicador; esto no funciona para números grandes; – tvanfosson

+0

IEE 754 Los dobles de 64 bits solo tienen una precisión de alrededor de 18 dígitos, de modo que si te metes en números donde multiplicar por 1000 provocará un desbordamiento (alrededor de 10^304), puedes estar seguro de que hay cero decimales en la representación. – paxdiablo

1

El tipo double es un número de punto flotante binario. Siempre hay aparentes inexactitudes al tratar con ellos como si fueran números de punto flotante decimal. No sé si alguna vez podrá escribir su función para que funcione de la manera que desee.

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

5

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

Inmutables, de precisión arbitraria, firmados decimales.Un BigDecimal consiste en de un entero de precisión arbitraria valor sin escalar y una escala de enteros de 32 bits . Si es cero o positivo, la escala es el número de dígitos a la derecha del punto decimal. Si es negativo, el valor sin escalar del número es multiplicado por diez a la potencia de negación de la escala. El valor de es el número representado por BigDecimal por lo tanto (unscaledValue × 10-scale).

scale puede definirse a través setScale(int)

0

No estoy seguro de que esto es realmente factible en general. Por ejemplo, ¿cuántos lugares decimales tiene 1.0e-13? ¿Qué ocurre si se produce un error de redondeo al hacer aritmética y realmente es solo 0 disfrazado? Si está activado, el otro lado está preguntando si hay algún dígitos distintos de cero en los primeros n cifras decimales se 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; 
    } 
+0

en realidad me ayudó su respuesta, pero como es ahora, tiene fallas ya que tiene que multiplicar 'd' antes de truncarlo para no perder dígitos significativos. Vea mi edición de la pregunta – Turismo

1

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

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

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