2010-07-14 4 views
7

Je suis terriblement contrarié par l'inexactitude des fonctions trigonométriques intrinsèques dans le CLR. Il est bien connu quePrécision de Math.Sin() et Math.Cos() dans C#

Math.Sin(Math.PI)=0.00000000000000012246063538223773 

au lieu de 0. Quelque chose de semblable se produit avec Math.Cos(Math.PI/2).

Mais quand je fais une longue série de calculs sur des cas particuliers à évaluer

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

et le résultat est nul pour x = 0,2, mais pas zéro pour x = 0,1 (essayer). Un autre problème est lorsque l'argument est un grand nombre, l'inexactitude devient proportionnellement grande. Donc, je me demande si quelqu'un a codé une meilleure représentation des fonctions trig en C# pour le partager avec le monde. Est-ce que le CLR appelle une bibliothèque mathématique C standard implémentant CORDIC ou quelque chose de similaire? link: wikipedia CORDIC

+8

À quel point croyez-vous que la représentation de pi en tant que double est exacte? –

+3

Si vous voulez des maths symboliques, faites des maths symboliques. Si vous utilisez des types à virgule flottante, vous obtenez une précision finie. – AakashM

+4

-1 pour ne pas "faire vos devoirs", et aussi pour penser que 'System.Math' fait partie de C# (indice: cela fait partie du.NET Framework). –

Répondre

2

Vous devez utiliser une bibliothèque décimale de précision arbitraire. (.Net 4.0 a un arbitrary integer class, mais pas un nombre décimal).

Quelques plus populaires sont disponibles:

18

Cela n'a rien à voir avec la précision des fonctions trigonométriques mais plutôt avec le système de type CLS. Selon la documentation, un double a une précision de 15-16 chiffres (ce qui est exactement ce que vous obtenez) donc vous ne pouvez pas être plus précis avec ce type. Donc, si vous voulez plus de précision, vous devrez créer un nouveau type capable de le stocker.

Notez également que vous ne devriez jamais être en train d'écrire un code comme ceci:

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

Vous devriez faire à la place:

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

Ceci est un résultat de précision à virgule flottante. Vous obtenez un certain nombre de chiffres significatifs, et tout ce qui ne peut pas être représenté exactement est approximé. Par exemple, pi n'est pas un nombre rationnel, et il est donc impossible d'obtenir une représentation exacte. Puisque vous ne pouvez pas obtenir la valeur exacte de pi, vous n'obtiendrez pas les sinus et les cosinus exacts des nombres, y compris pi (vous n'obtiendrez pas la plupart du temps les valeurs exactes des sinus et des cosinus).

La meilleure explication intermédiaire est "What Every Computer Scientist Should Know About Floating-Point Arithmetic". Si vous ne voulez pas entrer dans cela, rappelez-vous que les nombres à virgule flottante sont généralement des approximations, et que les calculs à virgule flottante sont comme des tas de sable en mouvement: avec tout ce que vous faites, vous perdez un peu de sable ramasser un peu de terre.

Si vous voulez une représentation exacte, vous devrez vous trouver un système d'algèbre symbolique.

+0

Je comprends si PI n'est pas défini exactement en raison de l'arithmétique IEEE-754, et je souhaite pouvoir conduire un système d'algèbre symbolique avec C#, mais je ne peux pas maintenant. – ja72

9

Je vous entends. Je suis terriblement agacé par l'imprécision de la division. L'autre jour, je l'ai fait:

Console.WriteLine(1.0/3.0); 

et je suis ,333333333333333, au lieu de la bonne réponse qui est 0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333 ...

Peut-être que vous voyez maintenant quel est le problème. Math.Pi n'est pas égal à pi tout plus de 1,0/3,0 est égal à un tiers. Les deux diffèrent de la vraie valeur par quelques centaines de quadrillionths, et donc tous les calculs que vous effectuez avec Math.Pi ou 1.0/3.0 vont également être décalés de quelques centaines de quadrillionths, y compris en prenant le sinus.

Si vous n'aimez pas cette approximation arithmétique est approximative alors ne pas utiliser l'arithmétique approximative. Utilisez l'arithmétique exacte. J'avais l'habitude d'utiliser Waterloo Maple quand j'avais besoin d'arithmétique exacte; peut-être devriez-vous acheter une copie de cela.

+0

Par curiosité, si l'on veut calculer l'arithmétique exacte en C#, serait-ce possible? Je ne sais pas ce que l'on peut utiliser pour faire ça? Pas même les décimales le couperaient, non? –

+2

@Joan: Oui, nous pouvons le faire facilement pour les entiers, et même pour les nombres rationnels (simplement stocker le numérateur/dénominateur comme grands entiers), et jeter des règles dans notre bibliothèque pour les nombres réels spécifiques que nous voulons: pi, e, square- racines, etc. Cependant, une bibliothèque pour l'arithmétique de précision arbitraire sur * tout * nombre réel imaginable est impossible; même en supposant que vous ayez un moyen de les stocker * (disons comme une formule qui nous donnera un chiffre arbitraire du nombre) *, il est impossible de comparer même deux nombres réels arbitraires pour l'égalité !! –

+1

@BlueRaja: en effet. Typiquement ce que vous faites dans ce cas est manipuler les quantités arithmétiques symboliquement; vous avez un symbole pour pi et un symbole pour e, de la même façon que vous avez un symbole pour 1, 2, 3, et ensuite vous codez toutes les identités arithmétiques et trigonométriques, comme ce sinus de pi est nul, et ainsi de suite. Les mathématiques symboliques sont difficiles. –

1

Je rejette l'idée des erreurs sont dues à l'arrondi. Que peut-on faire est de définir sin(x) comme suit, en utilisant l'expansion d'un Taylor avec 6 termes:

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; 
    } 

erreur typique est 1e-16 et pire des cas est 1e-11. C'est pire que le CLR, mais il est contrôlable en ajoutant plus de termes. Les bonnes nouvelles sont que pour les cas spéciaux dans le PO et pour Sin(45°) la réponse est exacte.

+0

Poste associé sur lequel les angles ont un trig. http://math.stackexchange.com/q/176889/3301 – ja72

+0

Si le seul problème est les cas spéciaux de l'OP, on pourrait aussi bien les écrire comme des instructions if-else au lieu d'aller avec une telle solution. Ces méthodes peuvent fonctionner lorsqu'elles sont appelées directement par ex. zéro, mais pas une valeur approximative. Si par exemple vous appelez Sin avec le résultat approximatif d'une opération précédente, cela ne fonctionnera pas (par exemple, parce que x est très petit, mais pas exactement zéro - même si "devrait" être). Le vrai problème ici est l'utilisation du double et l'attente d'obtenir des résultats exacts, qu'aucun algorithme ou formule au monde ne peut résoudre. – enzi