2012-03-08 20 views
11

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.

+4

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?) –

+3

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. –

+4

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. –

Respuesta

0

Le recomiendo que haga un solo paso, preferiblemente en modo de ensamblaje, a través de los cálculos mientras hace la misma aritmética en una calculadora. Debería poder determinar qué ordenamientos de cálculo arrojan resultados de calidad inferior a la esperada y cuáles funcionan. Aprenderá de esto y probablemente escriba cálculos mejor ordenados en el futuro.

Al final, dados los ejemplos de números que utiliza, probablemente deba aceptar el hecho de que no podrá hacer comparaciones de igualdad.

En cuanto al enfoque épsilon, normalmente necesita un épsilon para cada posible exponente. Para el formato de punto flotante de precisión simple necesitaría 256 valores de coma flotante de precisión simple ya que el exponente tiene 8 bits de ancho. Algunos exponentes serán el resultado de excepciones, pero por simplicidad, es mejor tener un vector de 256 miembros que hacer muchas pruebas también.

Una forma de hacer esto podría ser determinar su épsilon base en el caso donde el exponente es 0 i el valor que se va a comparar está en el rango 1.0 < = x < 2.0.Preferiblemente, el épsilon debe elegirse como base 2 adaptada, es decir, un valor que puede representarse exactamente en un formato de coma flotante de precisión única; de ese modo, usted sabe exactamente lo que está probando y no tendrá que pensar en problemas de redondeo en épsilon. bien. Para el exponente -1 usarías tu épsilon base dividido por dos, para -2 dividido por 4 y así sucesivamente. A medida que se acerca a las partes más bajas y más altas del rango del exponente, gradualmente se queda sin precisión, poco a poco, por lo que debe tener en cuenta que los valores extremos pueden hacer que falle el método épsilon.

4

He trabajado con modelos de simulación durante 2 años y el enfoque épsilon es la forma más segura de comparar tus carrozas.

0

Si tiene que ser flotante, entonces usar un valor épsilon puede ayudar, pero puede que no elimine todos los problemas. Yo recomendaría usar dobles para las manchas en el código que sabes con certeza tendrá variación.

Otra forma es usar flotadores para emular dobles, hay muchas técnicas y la más básica es usar 2 carrozas y hacer un poco de matemática para guardar la mayor parte del número en una carroza y el resto en el otro (vi una gran guía sobre esto, si lo encuentro lo vincularé).

3

En general, usar los valores épsilon adecuados es el camino a seguir si necesita usar números de coma flotante. Aquí hay algunas cosas que pueden ayudarlo:

  • Si sus valores están dentro de un rango conocido, usted y no necesita divisiones, es posible que pueda escalar el problema y usar operaciones exactas en enteros. En general, las condiciones no se aplican.
  • Una variación es usar números racionales para hacer cálculos exactos. Esto todavía tiene restricciones en las operaciones disponibles y, por lo general, tiene graves implicaciones de rendimiento: usted negocia el rendimiento para la precisión.
  • El modo de redondeo se puede cambiar. Esto se puede usar para calcular un intervalo en lugar de un valor individual (posiblemente con 3 valores resultantes de redondeo, redondeo y redondeo más cercanos). Nuevamente, no funcionará para todo, pero puede obtener un estimado de error a partir de esto.
  • También se puede seguir el valor y varias operaciones (posibles contadores múltiples) para estimar el tamaño actual del error.
  • Para posiblemente experimentar con diferentes representaciones numéricas (float, , intervalo, etc.) es posible que desee implementar su simulación como plantillas parametrizadas para el tipo numérico.
  • Hay muchos libros escritos sobre la estimación y minimización de errores cuando se usa la aritmética de punto flotante. Este es el tema de las matemáticas numéricas.

La mayoría de los casos conozco el experimento brevemente con algunos de los métodos mencionados anteriormente y concluyo que el modelo es impreciso de todos modos y no se moleste con el esfuerzo. Además, hacer algo más que usar float puede producir mejores resultados, pero es demasiado lento, incluso usando double debido a la huella de memoria duplicada y la menor oportunidad de usar operaciones SIMD.

+0

En algunos casos, la "normalización" (?) Puede ayudar. Supongamos que un objeto se mueve o gira y sus nuevas coordenadas se recalculan cada vez en función de las últimas coordenadas. Con el tiempo, a medida que los errores se acumulan, el objeto puede comenzar a perder su forma o tamaño original. Esto puede solucionarse recalculando/ajustando las coordenadas de forma apropiada (cada vez o de vez en cuando), teniendo en cuenta la forma y el tamaño esperados. –

0

Ciertamente deberías usar dobles en lugar de flotadores. Esto probablemente reducirá significativamente la cantidad de nodos volteados.

En general, usar un umbral épsilon solo es útil cuando se comparan dos números de punto flotante para la igualdad, no cuando se comparan para ver cuál es más grande. Entonces (para la mayoría de los modelos, al menos) el uso de épsilon no le generará nada en absoluto; solo cambiará el conjunto de nodos volteados, no hará que ese conjunto sea más pequeño. Si su modelo en sí mismo es caótico, entonces es caótico.

Cuestiones relacionadas