Estoy escribiendo un programa de simulación que procede en pasos discretos. La simulación consta de muchos nodos, cada uno de los cuales tiene un valor de coma flotante asociado que se vuelve a calcular en cada paso. El resultado puede ser positivo, negativo o cero.¿Cómo puedo obtener un comportamiento consistente del programa cuando uso flotadores?
En el caso donde el resultado es cero o menos, sucede algo. Hasta el momento esto parece sencillo - que sólo puede hacer algo como esto para cada nodo:
if (value <= 0.0f) something_happens();
ha surgido un problema, sin embargo, después de algunos cambios recientes que he realizado en el programa en el que reorganizó el orden en que ciertos cálculos están hechos. En un mundo perfecto, los valores seguirían siendo los mismos después de este cambio de disposición, pero a causa de la imprecisión de la representación en coma flotante, resultan ligeramente diferentes. Como los cálculos para cada paso dependen de los resultados del paso anterior, estas pequeñas variaciones en los resultados pueden acumularse en variaciones más grandes a medida que avanza la simulación.
He aquí un sencillo programa de ejemplo que demuestra los fenómenos que estoy describiendo:
float f1 = 0.000001f, f2 = 0.000002f;
f1 += 0.000004f; // This part happens first here
f1 += (f2 * 0.000003f);
printf("%.16f\n", f1);
f1 = 0.000001f, f2 = 0.000002f;
f1 += (f2 * 0.000003f);
f1 += 0.000004f; // This time this happens second
printf("%.16f\n", f1);
La salida de este programa es
0.0000050000057854
0.0000050000062402
a pesar de que la adición es conmutativa por lo tanto los resultados deben ser los mismos . Nota: entiendo perfectamente bien por qué sucede esto: ese no es el problema. El problema es que estas variaciones pueden significar que a veces un valor que solía ser negativo en el paso N, activando something_happens(), ahora puede salir negativo uno o dos pasos más temprano o más tarde, lo que puede llevar a resultados de simulación muy diferentes porque something_happens() tiene un gran efecto.
Lo que quiero saber es si hay una buena manera de decidir cuándo se activará something_happens() que no va a verse afectado por las pequeñas variaciones en los resultados del cálculo que resultan de las operaciones de reordenación para que el comportamiento de las versiones más nuevas de mi programa serán consistentes con las versiones anteriores.
La única solución hasta ahora he sido capaz de pensar es utilizar algún valor épsilon así:
if (value < epsilon) something_happens();
pero debido a las pequeñas variaciones en los resultados se acumulan con el tiempo que necesito para hacer bastante épsilon grande (hablando relativamente) para asegurar que las variaciones no den como resultado que something_happens() se active en un paso diferente. ¿Hay una mejor manera?
He leído this excellent article en comparación de coma flotante, pero no veo cómo cualquiera de los métodos de comparación descritos podría ayudarme en esta situación.
Nota: El uso de valores enteros en cambio no es una opción.
Editar la posibilidad de utilizar dobles en lugar de los flotadores se ha planteado. Esto no resolvería mi problema ya que las variaciones seguirían allí, solo serían de una magnitud menor.
Si las pequeñas variaciones provocan grandes cambios en la salida, ¿no es solo que le dice que sus resultados tienen poca precisión? (También: ¿por qué flotar no el doble?) –
Tenga cuidado: 'printf ("% .16f \ n ", f1);' este es un efecto secundario inesperado: convertirá su flotador en una doble suma de dígitos no significativos. Un flotador como, creo, una precisión de 7 dígitos a máx. –
La forma estándar de sumar valores de coma flotante es: para ordenarlos y sumar de menor a mayor, pierde la menor precisión. También use doble no flotante. –