c++ - programacion - punto flotante normalizado
¿Cómo lidiar con una precisión excesiva en cálculos de coma flotante? (5)
Asegúrate de hacer que esa verificación sea un valor absoluto. Debe ser un épsilon alrededor de cero, arriba y abajo.
En mi simulación numérica, tengo un código similar al siguiente fragmento
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 compute en una precisión mayor que el doble ("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 doble precisión se convierta en 0. (Y no hay garantía de que x no se redondee en un punto arbitrario en el tiempo).
¿Hay alguna forma de realizar esta comparación que
- es portátil,
- funciona en código que se inserta,
- no tiene impacto en el rendimiento 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 los denormales pueden ralentizar un cálculo, pero no esperaba que fueran más lentos para moverse y comparar).
Actualización: una alternativa es usar volatile
para forzar x
en la memoria antes de 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 también puede generar una sobrecarga considerable.
Actualización: el problema con cualquier tolerancia es que es bastante arbitraria, es decir, 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.
Bueno, GCC tiene una bandera, -fexcess-precision que causa el problema que está discutiendo. También tiene una bandera, -ffloat-store, que resuelve el problema que está discutiendo.
"No almacene variables de coma flotante en los registros. Esto previene una excesiva 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 tenga un impacto en el rendimiento, pero el impacto probablemente no sea demasiado caro. La búsqueda aleatoria en Google sugiere que cuesta alrededor del 20%. En realidad, no creo que haya una solución que sea a la vez portátil y no tenga un impacto en el rendimiento, ya que obligar a un chip a no usar una precisión excesiva a menudo implicará un funcionamiento no libre. Sin embargo, esta es probablemente la solución que desea.
Como dijo Arkadiy en los comentarios, un lanzamiento explícito ((double)x) <= 0.0
debería funcionar, al menos según el estándar.
C99: TC3, 5.2.4.2.2 §8:
Excepto para asignación y conversión (que eliminan todo el rango y precisión extra), los valores de operaciones con operandos flotantes y valores sujetos a las conversiones aritméticas habituales y de 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 usar los indicadores -mpc32
, -mpc64
y -mpc80
para establecer la precisión de las operaciones de coma flotante en precisión doble simple, doble y extendida.
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, lo que permite 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 verificar si puede acelerar la comparación con el menor valor subnormal usando el long double
explícitamente y el valor en caché, es decir,
const long double dbl_denorm_min = static_cast<long double>(std::numeric_limits<double>::denorm_min());
y luego compara
x < dbl_denorm_min
Supongo que un compilador decente haría esto automáticamente, pero uno nunca sabe ...
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í.