2011-08-10 9 views
5

Para el siguiente código (Java):¿Cómo funciona la conversión del doble al int en Java?

double d = (double) m/n; //m and n are integers, n>0 
int i = (int) (d * n); 

i == m 

es la última expresión siempre es cierto? No si es cierto es esto siempre ?:

i = (int) Math.round(d * n); 

i == m 
+1

No es una pregunta tonta; la pregunta plantea algunos problemas sutiles acerca de la aritmética de coma flotante y la "capacidad de recuperación" de los enteros. – Nayuki

+0

muchas preguntas flotantes en este sitio últimamente - hmm ... –

Respuesta

4

La segunda pregunta que hace se refiere a cuán grande es ulp en Java.

Si el ulp supera 1/(n), redondear la multiplicación no recuperaría el original dividido int. Típicamente, los ulps más grandes están asociados con valores dobles más grandes. Un ulp asociado con un doble comienza a exceder 1 en alrededor de 9E15; si tus dobles recuperados estaban por allí, entonces podrías encontrar problemas con round() que no obtuviera la respuesta esperada. Sin embargo, como está trabajando con valores int, el valor más grande del numerador de su división será Integer.MAX_VALUE.

las siguientes pruebas del programa todos los valores enteros positivos de n para ver cuál hace que el mayor potencial de error de redondeo cuando se trata de recuperar el int dividida:

public static void main(String[] args) 
    { 
    // start with large number 
    int m = Integer.MAX_VALUE; 
    double d = 0; 

    double largestError = 0; 
    int bigErrorCause = -1; 
    for (int n = 1; n < Integer.MAX_VALUE; n++) 
    { 
     d = (double) m/n; 
     double possibleError = Math.ulp(d) * n; 
     if (possibleError > largestError) 
     { 
     largestError = possibleError; 
     bigErrorCause = n; 
     } 
    } 
    System.out.println("int " + bigErrorCause + " causes at most " 
     + largestError + " error"); 
    } 

la salida es:

int 1073741823 causa a lo sumo 4.768371577590358E-7 error

Redondeando eso usando Math.round, entonces fundir a int debería recuperar el int original.

1

Matemáticamente debe ser verdad. Sin embargo, es probable que obtenga errores de redondeo de coma flotante que lo harán falso. Casi nunca debe comparar los números de precisión de punto flotante usando ==.

Usted está mucho mejor de compararlos utilizando un umbral de esta manera:

Math.abs(d*n - m) < 0.000001; 

Tenga en cuenta que las dos afirmaciones deben ser equivalentes

i = (int) (d * n); 
i = (int) Math.round(d * n); 

Sin embargo, por ejemplo, si d=3/2 y n=2, flotando los errores de puntos pueden dar como resultado i=2.999999999999 que después del truncamiento/redondeo es 2.

+3

Su razonamiento sobre el truncamiento es bueno. Pero su ejemplo es malo, porque la división de coma flotante por 2 siempre es exacta (excepto por debajo del flujo). ;-) – Nayuki

1

El primero es d Efectivamente no siempre es verdad. El segundo diría que sí, es cierto, pero solo porque no puedo pensar en un contraejemplo.

Si n es muy grande, podría ser falso, realmente no estoy seguro. Sé que será cierto al menos el 99% del tiempo sin embargo.

5

int i = (int) (d * n); i == m;

Esto es falso para m = 1, n = 49.

i = (int) Math.round(d * n); i == m;

Mi intuición me dice que debe ser cierto, pero puede ser difícil de demostrar de forma rigurosa.

+0

Puedes verificar mi extracto incluso en JavaScript. Puede ingresar esto en su navegador: 'javascript: 1/49 * 49', que da 0.9999 .... – Nayuki

+2

+1 Dado que 'double' tiene 53 bits de precisión y lo divide y multiplica por menos de' 2^31', el resultado debería estar en menos de '1/2^21 = 2 * 1/2^22' (el factor de 2 es hacer dos operaciones). Entonces el redondeo será al entero exacto por un amplio margen. – starblue

+0

Eliminé mi publicación porque creo que estaba equivocado sobre el segundo caso, buena respuesta (ya te di mi +1) – MByD