2010-07-14 204 views
7

Estoy terriblemente molesto por la inexactitud de las funciones trigonométricas intrínsecas en el CLR. Es bien sabido quePrecisión de Math.Sin() y Math.Cos() en C#

Math.Sin(Math.PI)=0.00000000000000012246063538223773 

en lugar de 0. Algo similar ocurre con Math.Cos(Math.PI/2).

Pero cuando estoy haciendo una larga serie de cálculos que en casos especiales se evalúan como

Math.Sin(Math.PI/2+x)-Math.Cos(x) 

y el resultado es cero para x = 0,2, pero no cero para x = 0,1 (probarlo). Otro problema es cuando el argumento es un número grande, la inexactitud es proporcionalmente grande.

Así que me pregunto si alguien ha codificado una mejor representación de las funciones trigonométricas en C# para compartirlas con el resto del mundo. ¿El CLR llama a alguna biblioteca matemática C estándar que implemente CORDIC o algo similar? link: wikipedia CORDIC

+8

¿Qué tan exacta cree que es la representación de pi como un doble? –

+3

Si quiere matemática simbólica, haga matemática simbólica. Si usa tipos de coma flotante, obtiene precisión finita. – AakashM

+4

-1 para "no hacer la tarea", y también para pensar que 'System.Math' es parte de C# (pista: es parte de.NET Framework). –

Respuesta

2

Debe usar una biblioteca de decimales de precisión arbitraria. (.Net 4.0 tiene un arbitrary integer class, pero no es decimal).

Algunas de las más populares están disponibles:

18

Esto no tiene nada que ver con la precisión de las funciones trigonométricas, pero más con el sistema de tipo CLS. Según la documentación, un double tiene una precisión de 15-16 dígitos (que es exactamente lo que obtienes) por lo que no puedes ser más preciso con este tipo. Entonces, si quiere más precisión, necesitará crear un nuevo tipo que sea capaz de almacenarlo.

Observe también que nunca se debe escribir un código como este:

double d = CalcFromSomewhere(); 
if (d == 0) 
{ 
    DoSomething(); 
} 

que debe hacer en su lugar:

double d = CalcFromSomewhere(); 
double epsilon = 1e-5; // define the precision you are working with 
if (Math.Abs(d) < epsilon) 
{ 
    DoSomething(); 
} 
6

Este es un resultado de coma flotante de precisión. Obtiene un cierto número de dígitos significativos, y todo lo que no se puede representar exactamente es aproximado. Por ejemplo, pi no es un número racional, por lo que es imposible obtener una representación exacta. Como no puedes obtener un valor exacto de pi, no obtendrás los senos y cosenos exactos de los números, incluido pi (tampoco obtendrás los valores exactos de senos y cosenos la mayor parte del tiempo).

La mejor explicación intermedia es "What Every Computer Scientist Should Know About Floating-Point Arithmetic". Si no quiere entrar en eso, recuerde que los números flotantes son generalmente aproximaciones, y que los cálculos de punto flotante son como mover montones de arena en el suelo: con todo lo que hace con ellos, pierde un poco de arena y recoge un poco de suciedad.

Si quiere una representación exacta, necesitará encontrar un sistema simbólico de álgebra.

+0

Entiendo si PI no está definido exactamente debido a la aritmética IEEE-754, y me gustaría poder conducir un sistema de álgebra simbólica con C#, pero no puedo ahora. – ja72

9

Te escucho. Estoy terriblemente molesto por la inexactitud de la división. El otro día hice:

Console.WriteLine(1.0/3.0); 

y llegué ,333333333333333, en lugar de la respuesta correcta, que es 0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333 ...

Quizás ahora vea cuál es el problema. Math.Pi no es igual a pi más de 1.0/3.0 es igual a un tercio. Ambos difieren del valor real en algunos cientos cuadrillonésimos, y por lo tanto, cualquier cálculo que realices con Math.Pi o 1.0/3.0 también estará desactivado en algunos cientos de cuadrillonésimos, incluido tomar el seno.

Si no te gusta esa aritmética aproximada es aproximadamente, entonces no uses la aritmética aproximada. Usa aritmética exacta. Solía ​​usar Waterloo Maple cuando necesitaba una aritmética exacta; tal vez deberías comprar una copia de eso.

+0

Por curiosidad, si uno quiere calcular la aritmética exacta en C#, ¿sería posible? ¿Simplemente no sé lo que uno puede usar para hacer eso? Ni siquiera los decimales lo cortarían, ¿verdad? –

+2

@Joan: Sí, podemos hacerlo fácilmente para enteros, e incluso para números racionales (solo almacena numerador/denominador como enteros grandes), y lanzamos reglas a nuestra biblioteca para cualquier número real específico que queramos: pi, e, cuadrado- raíces, etc. Sin embargo, una biblioteca de aritmética de precisión arbitraria en * cualquier * número real imaginable es imposible; incluso suponiendo que tuviera alguna forma de almacenarlos * (por ejemplo, como una fórmula que nos dará un dígito arbitrario del número) *, es computacionalmente imposible incluso comparar dos números reales arbitrarios para la igualdad. –

+1

@BlueRaja: de hecho. Normalmente, lo que se hace en ese caso es manipular las cantidades aritméticas simbólicamente; tiene un símbolo para pi y un símbolo para e, de la misma manera que tiene un símbolo para 1, 2, 3, y luego codifica todas las identidades aritméticas y trigonométricas, como ese seno de pi es cero, y así sucesivamente. La matemática simbólica es difícil. –

1

Rechazo la idea de los errores se deben a redondeo. Lo que puede hacerse es definir sin(x) de la siguiente manera, usando la expansión de Taylor con 6 términos:

const double π=Math.PI; 
    const double π2=Math.PI/2; 
    const double π4=Math.PI/4; 

    public static double Sin(double x) 
    { 

     if (x==0) { return 0; } 
     if (x<0) { return -Sin(-x); } 
     if (x>π) { return -Sin(x-π); } 
     if (x>π4) { return Cos(π2-x); } 

     double x2=x*x; 

     return x*(x2/6*(x2/20*(x2/42*(x2/72*(x2/110*(x2/156-1)+1)-1)+1)-1)+1); 
    } 

    public static double Cos(double x) 
    { 
     if (x==0) { return 1; } 
     if (x<0) { return Cos(-x); } 
     if (x>π) { return -Cos(x-π); } 
     if (x>π4) { return Sin(π2-x); } 

     double x2=x*x; 

     return x2/2*(x2/12*(x2/30*(x2/56*(x2/90*(x2/132-1)+1)-1)+1)-1)+1; 
    } 

error típico es 1e-16 y el peor caso es 1e-11. Es peor que el CLR, pero es controlable agregando más términos. La buena noticia es que para los casos especiales en el OP y para Sin(45°) la respuesta es exacta.

+0

Publicación relacionada sobre qué ángulos tienen trigonometría exacta. valores http://math.stackexchange.com/q/176889/3301 – ja72

+0

Si el único problema son los casos especiales del OP, uno también podría escribirlos como sentencias if-else en lugar de ir con tal solución. Estos métodos pueden funcionar cuando se los llama directamente, p. cero, pero no un valor aproximado. Si, por ejemplo, Si llama a Sin con el resultado aproximado de una operación anterior, no funcionará (por ejemplo, porque x es muy pequeño, pero no exactamente cero, incluso si "debería" ser). El verdadero problema aquí es el uso del doble y la expectativa de obtener resultados exactos, que ningún algoritmo o fórmula en el mundo puede arreglar. – enzi

Cuestiones relacionadas