Al crear una aplicación que se ocupa de muchos cálculos matemáticos, me he encontrado con el problema de que ciertos números causan errores de redondeo.
Si bien entiendo que el punto flotante no es exacto , el problema es ¿cómo trato con los números exactos para asegurarme de que cuando se realicen los cálculos en ellos, el redondeo del punto flotante no cause ningún problema?
distanceTraveled(startVel, duration, acceleration)
sería probado.Respuestas:
Existen tres enfoques fundamentales para crear tipos numéricos alternativos que estén libres de redondeo de punto flotante. El tema común con estos es que usan matemáticas enteras en su lugar de varias maneras.
Racionales
Representa el número como una parte entera y número racional con un numerador y un denominador. El número
15.589
se representaría comow: 15; n: 589; d:1000
.Cuando se agrega a 0.25 (que es
w: 0; n: 1; d: 4
), esto implica calcular el MCM y luego sumar los dos números. Esto funciona bien para muchas situaciones, aunque puede resultar en números muy grandes cuando se trabaja con muchos números racionales que son relativamente primos entre sí.Punto fijo
Tienes toda la parte y la parte decimal. Todos los números están redondeados (existe esa palabra, pero ya sabes dónde está) con esa precisión. Por ejemplo, podría tener un punto fijo con 3 puntos decimales.
15.589
+ se0.250
convierte en suma589 + 250 % 1000
para la parte decimal (y luego cualquier transferencia a la parte completa). Esto funciona muy bien con las bases de datos existentes. Como se mencionó, hay redondeo, pero usted sabe dónde está y puede especificarlo de manera que sea más preciso de lo necesario (solo está midiendo a 3 puntos decimales, por lo que debe fijarlo en 4).Punto fijo flotante
Almacenar un valor y la precisión.
15.589
se almacena como15589
para el valor y3
para la precisión, mientras que0.25
se almacena como25
y2
. Esto puede manejar precisión arbitraria. Creo que esto es lo que usa la parte interna de los usos BigDecimal de Java (no lo he mirado recientemente). En algún momento, querrá recuperarlo de este formato y mostrarlo, y eso puede implicar redondeo (nuevamente, usted controla dónde está).Una vez que determine la opción para la representación, puede encontrar bibliotecas de terceros existentes que usen esto o escribir la suya propia. Al escribir el suyo, asegúrese de probarlo en la unidad y asegúrese de estar haciendo los cálculos correctamente.
fuente
Si los valores de punto flotante tienen problemas de redondeo, y no desea tener problemas de redondeo, lógicamente se deduce que el único curso de acción es no usar valores de punto flotante.
Ahora la pregunta es, "¿cómo hago matemáticas que involucran valores no enteros sin variables de punto flotante?" La respuesta es con tipos de datos de precisión arbitraria . Los cálculos son más lentos porque tienen que implementarse en software en lugar de en hardware, pero son precisos. No dijo qué idioma está utilizando, por lo que no puedo recomendar un paquete, pero hay bibliotecas de precisión arbitrarias disponibles para los lenguajes de programación más populares.
fuente
lot of mathematical calculations
no es útil ni las respuestas dadas. En la gran mayoría de los casos (si no se trata de divisas), la flotación debería ser suficiente.La aritmética de coma flotante suele ser bastante precisa (15 dígitos decimales para a
double
) y bastante flexible. Los problemas surgen cuando haces matemáticas que reducen significativamente la cantidad de dígitos de precisión. Aquí hay unos ejemplos:Cancelación en la resta:
1234567890.12345 - 1234567890.12300
el resultado0.0045
tiene solo dos dígitos decimales de precisión. Esto golpea siempre que restas dos números de magnitud similar.Ingestión de precisión: se
1234567890.12345 + 0.123456789012345
evalúa1234567890.24691
, se pierden los últimos diez dígitos del segundo operando.Multiplicaciones: si multiplica dos números de 15 dígitos, el resultado tiene 30 dígitos que deben almacenarse. Pero no puede almacenarlos, por lo que se pierden los últimos 15 bits. Esto es especialmente molesto cuando se combina con un
sqrt()
(como ensqrt(x*x + y*y)
: El resultado solo tendrá 7,5 dígitos de precisión.Estas son las principales trampas que debe tener en cuenta. Y una vez que los conozca, puede intentar formular sus matemáticas de una manera que las evite. Por ejemplo, si necesita incrementar un valor una y otra vez en un bucle, evite hacer esto:
Después de algunas iteraciones, el más grande
f
se tragará parte de la precisión dedf
. Peor aún, los errores se sumarán, lo que conducirá a la situación contraintuitiva de que un tamaño más pequeñodf
puede conducir a peores resultados generales. Mejor escribe esto:Como está combinando los incrementos en una sola multiplicación, el resultado
f
será preciso a 15 dígitos decimales.Este es solo un ejemplo, hay otras formas de evitar la pérdida de precisión debido a otras razones. Pero ya ayuda mucho pensar en la magnitud de los valores involucrados e imaginar qué sucedería si hiciera sus cálculos con lápiz y papel, redondeando a un número fijo de dígitos después de cada paso.
fuente
Cómo asegurarse de que no tiene problemas: aprenda sobre problemas aritméticos de punto flotante, o contrate a alguien que lo tenga, o use algo de sentido común.
El primer problema es la precisión. En muchos idiomas tiene "flotante" y "doble" (doble posición para "doble precisión"), y en muchos casos "flotante" le da una precisión de aproximadamente 7 dígitos, mientras que el doble le da 15. El sentido común es que si tiene un situación en la que la precisión podría ser un problema, 15 dígitos es muchísimo mejor que 7 dígitos. En muchas situaciones ligeramente problemáticas, usar "doble" significa que te saldrás con la tuya, y "flotar" significa que no. Digamos que la capitalización de mercado de una empresa es de 700 mil millones de dólares. Representa esto en flotante, y el bit más bajo es $ 65536. Represente usando doble, y el bit más bajo es de aproximadamente 0.012 centavos. Entonces, a menos que realmente sepas lo que estás haciendo, usarás doble, no flotante.
El segundo problema es más una cuestión de principios. Si hace dos cálculos diferentes que deberían dar el mismo resultado, a menudo no lo hacen debido a errores de redondeo. Dos resultados que deberían ser iguales serán "casi iguales". Si dos resultados están juntos, los valores reales pueden ser iguales. O tal vez no lo sean. Debe tener eso en cuenta y debe escribir y usar funciones que digan "x es definitivamente mayor que y" o "x es definitivamente menor que y" o "x e y podrían ser iguales".
Este problema empeora mucho si usa el redondeo, por ejemplo, "redondear x al entero más cercano". Si multiplica 120 * 0.05, el resultado debería ser 6, pero lo que obtendrá es "algún número muy cercano a 6". Si luego "redondea al número entero más cercano", ese "número muy cercano a 6" podría ser "ligeramente menor que 6" y redondearse a 5. Y tenga en cuenta que no importa la precisión que tenga. No importa qué tan cerca de 6 esté su resultado, siempre que sea menor que 6.
Y tercero, algunos problemas son difíciles . Eso significa que no hay una regla rápida y fácil. Si su compilador admite "doble largo" con más precisión, puede usar "doble largo" y ver si hace la diferencia. Si no hay diferencia, entonces estás bien o tienes un problema realmente complicado. Si hace el tipo de diferencia que esperarías (como un cambio en el 12º decimal), entonces probablemente estés bien. Si realmente cambia sus resultados, entonces tiene un problema. Pedir ayuda.
fuente
La mayoría de las personas comete el error cuando ven el doble y gritan BigDecimal, cuando en realidad acaban de trasladar el problema a otra parte. Doble da bit de signo: 1 bit, ancho de exponente: 11 bits. Precisión significativa: 53 bits (52 almacenados explícitamente). Debido a la naturaleza del doble, cuanto mayor sea el número entero, perderá una precisión relativa. Para calcular la precisión relativa que usamos aquí está abajo.
Precisión relativa del doble en el cálculo usamos el siguiente foluma 2 ^ E <= abs (X) <2 ^ (E + 1)
epsilon = 2 ^ (E-10)% Para un flotante de 16 bits (media precisión)
En otras palabras, si desea una precisión de +/- 0.5 (o 2 ^ -1), el tamaño máximo que puede ser el número es 2 ^ 52. Más grande que esto y la distancia entre los números de coma flotante es mayor que 0.5.
Si desea una precisión de +/- 0.0005 (aproximadamente 2 ^ -11), el tamaño máximo que puede ser el número es 2 ^ 42. Más grande que esto y la distancia entre los números de coma flotante es mayor que 0.0005.
Realmente no puedo dar una mejor respuesta que esta. El usuario necesitará determinar qué precisión quiere al realizar el cálculo necesario y su valor unitario (metros, pies, pulgadas, mm, cm). Para la gran mayoría de los casos, flotar será suficiente para simulaciones simples dependiendo de la escala del mundo que pretendes simular.
Aunque es algo que decir, si solo pretende simular un mundo de 100 metros por 100 metros, tendrá un lugar del orden de precisión cercano a 2 ^ -45. Esto ni siquiera explica cómo la FPU moderna dentro de las CPU realizará cálculos fuera del tamaño de tipo nativo y solo después de que se complete el cálculo se redondeará (dependiendo del modo de redondeo de FPU) al tamaño de tipo nativo.
fuente