2009-02-02 21 views
9

En mi simulación numérica Tengo un código similar al del siguiente fragmento¿Cómo lidiar con el exceso de precisión en los cálculos de coma flotante?

double x; 
do { 
    x = /* some computation */; 
} while (x <= 0.0); 
/* some algorithm that requires x to be (precisely) larger than 0 */ 

Con ciertos compiladores (por ejemplo gcc) en ciertas plataformas (por ejemplo, Linux, x87 matemáticas) es posible que x se calcula superior al doble precisión ("con precisión excesiva"). (Actualización: Cuando hablo de precisión aquí, me refiero a precisión/y/rango.) En estas circunstancias es concebible que la comparación (x <= 0) devuelva falso aunque la próxima vez que x se redondea a precisión doble se vuelva 0 . (Y no hay garantía de que x no se redondea hacia abajo en un punto arbitrario en el tiempo.)

¿hay alguna manera de realizar esta comparación que

  • es portátil,
  • obras en código que se inserta,
  • no tiene ningún impacto en el rendimiento t y
  • no excluye algún rango arbitrario (0, eps)?

Intenté usar (x < std::numeric_limits<double>::denorm_min()) pero eso pareció ralentizar significativamente el ciclo cuando se trabajaba con matemáticas SSE2. (Sé que denormals pueden ralentizar un cálculo, pero no me esperaba que sean más lentos en movimiento justo alrededor y comparar.)

Actualización: Una alternativa es utilizar volatile a la fuerza en la memoria antes x la comparación, por ejemplo escribiendo

} while (*((volatile double*)&x) <= 0.0); 

Sin embargo, dependiendo de la aplicación y las optimizaciones aplicadas por el compilador, esta solución puede introducir un rendimiento evidente también.

Actualización: El problema con cualquier tolerancia es que es bastante arbitraria, es decir, que depende de la aplicación o contexto específico. Preferiría hacer la comparación sin demasiada precisión, para no tener que hacer suposiciones adicionales o introducir algunos epsilons arbitrarios en la documentación de las funciones de mi biblioteca.

+0

Pregunta interesante, la primera vez que he oído hablar de alguien quejándose de demasiada precisión. – dsimcha

+0

¿Qué sucede cuando agrega un molde explícito a la comparación, es decir, '((double) x) <= 0.0'? – Christoph

+0

Quizás también sea relevante: http://stackoverflow.com/questions/322797/problem-with-floating-point-precision-when-moving-from-i386-to8x4 – Christoph

Respuesta

1

Me pregunto si tiene el criterio de detención correcto. Parece que x < = 0 es una condición de excepción, pero no una condición de terminación y que la condición de terminación es más fácil de satisfacer. Tal vez debería haber una declaración de interrupción dentro de su ciclo while que detiene la iteración cuando se alcanza cierta tolerancia. Por ejemplo, una gran cantidad de algoritmo finaliza cuando dos iteraciones sucesivas son suficientemente cercanas entre sí.

+0

No, en este caso, realmente es una condición de terminación (o la negación lógica de la condición de terminación). Por supuesto, podría verificar en otro momento si se produjo alguna división por cero, pero en este caso (y muchos similares) realmente necesito que mi función no devuelva ningún valor menor o igual a 0 – Stephan

0

Bueno, GCC tiene una bandera, -fexceso-precisión que causa el problema que está discutiendo. También tiene una bandera, -ffloat-store, que resuelve el problema que está discutiendo.

"No almacene las variables de punto flotante en los registros. Esto previene el exceso de precisión indeseable en máquinas como la 68000 donde los registros flotantes (del 68881) mantienen más precisión que la que se supone que tiene un doble."

dudo que la solución no tiene impacto en el rendimiento, pero el impacto no es probablemente demasiado caro. Googlear aleatoria sugiere que cuesta alrededor de 20%. En realidad, no creo que haya es una solución que es a la vez portátil y no tiene impacto en el rendimiento, ya que forzar un chip para no utilizar el exceso de precisión a menudo se va a implicar alguna operación que no sea libre. sin embargo, esta es probablemente la solución que desea.

+0

en plataformas basadas en x86 con El soporte SSE2, usando "-mfpmath = sse" debería resolver el problema sin ningún impacto negativo en el rendimiento. Sin embargo, prescribir opciones de compilador no estándar no es realmente portátil en mi libro. – Stephan

0

Asegúrese de hacer el registro de entrada un valor absoluto. se necesita ser un épsilon alrededor de cero, arriba y abajo.

+0

No entiendo, '(x Stephan

+0

El problema no es el problema de precisión de punto flotante habitual, sino que el póster original obtiene "(x> = 0)" para devolver falso, en una condición de bucle, cuando "x == 0" es verdadero fuera del bucle. –

5

Como dijo Arkadiy en los comentarios, un molde explícito ((double)x) <= 0.0debería funcionar - al menos de acuerdo con la norma.

C99: TC3, 5.2.4.2.2 § 8:

excepción de misiones y de yeso (que quitar toda la gama extra y precisión), los valores de las operaciones con operandos flotantes y valores sujetos a la habitual las conversiones aritméticas y las constantes flotantes se evalúan a un formato cuyo rango y precisión pueden ser mayores que los requeridos por el tipo. [...]


Si está utilizando GCC en x86, puede utilizar las banderas -mpc32, -mpc64 y -mpc80 para establecer la precisión de las operaciones de punto flotante de simple, doble y extendida de doble precisión .

+0

Pero depender de C99 no es realmente portátil. Si es así, solo las versiones más recientes de GCC cumplirán esta garantía, por no mencionar todos los demás compiladores ... – Stephan

+0

Todo es una pregunta acerca de qué tan portátil debe ser su código. Me gustaría ir con el estándar y simplemente probar los compiladores con más probabilidades de ser utilizados. Si no son compatibles, agregaría indicadores específicos del compilador al archivo MAKE ... – Christoph

+0

PD: "En todo caso, solo las versiones GCC más recientes cumplirán esta garantía, por no mencionar todos los demás compiladores" - did lo prueba, o fue solo una suposición basada en el soporte incompleto C99 en la mayoría (o incluso todos) de los compiladores actuales? – Christoph

2

En su pregunta, indicó que usar volatile funcionará, pero que habrá un gran golpe de rendimiento. ¿Qué pasa con el uso de la variable volatile solo durante la comparación, permitiendo que x se mantenga en un registro?

double x; /* might have excess precision */ 
volatile double x_dbl; /* guaranteed to be double precision */ 
do { 
    x = /* some computation */; 
    x_dbl = x; 
} while (x_dbl <= 0.0); 

También debe comprobar si se puede acelerar la comparación con el valor inferior a la normal más pequeño utilizando long double explícita y almacenar en caché este valor, es decir

const long double dbl_denorm_min = static_cast<long double>(std::numeric_limits<double>::denorm_min()); 

y luego comparar

x < dbl_denorm_min 

Supongo que un compilador decente haría esto automáticamente, pero uno nunca sabe ...

+0

Depende un poco del compilador y del ciclo específico. Parece ser más rápido que compararlo con un valor denormal, pero aún así presenta una sobrecarga notable en las plataformas sin una precisión excesiva. – Stephan

+0

Sin embargo, probablemente vaya con alguna definición de macro/preprocesador que se expanda a 'while (* ((double volátil *) & x) <= 0.0);' en plataformas con una precisión excesiva potencial. – Stephan

+0

Las comparaciones denormales solo ralentizan las matemáticas SSE2, no las matemáticas x87 donde los dobles largos adecuados están disponibles. – Stephan

Cuestiones relacionadas