2010-07-14 6 views
7

Ich bin schrecklich verärgert über die Ungenauigkeit der intrinsischen Triggerfunktionen in der CLR. Es ist gut, dassGenauigkeit von Math.Sin() und Math.Cos() in C#

Math.Sin(Math.PI)=0.00000000000000012246063538223773 

statt 0 Etwas Ähnliches geschieht mit Math.Cos(Math.PI/2).

Aber wenn ich eine lange Reihe von Berechnungen mache, die auf Sonderfälle zu bewerten

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

und das Ergebnis Null ist für x = 0,2, aber nicht Null für x = 0,1 (versuchen Sie es). Ein anderes Problem ist, wenn das Argument eine große Zahl ist, wird die Ungenauigkeit proportional groß.

Also frage ich mich, ob jemand etwas bessere Darstellung der trigonometrischen Funktionen in C# für den Austausch mit der Welt codiert hat. Ruft die CLR eine Standard-C-Mathematikbibliothek auf, die CORDIC oder etwas Ähnliches implementiert? Link: wikipedia CORDIC

+8

Wie genau glauben Sie, dass die Darstellung von pi doppelt ist? –

+3

Wenn Sie symbolische Mathematik wollen, tun Sie symbolische Mathematik. Wenn Sie Fließkommatypen verwenden, erhalten Sie eine endliche Genauigkeit. – AakashM

+4

-1 für nicht "Hausaufgaben machen", und auch für das Denken, dass 'System.Math' Teil von C# ist (Hinweis: es ist Teil der.NET Framework). –

Antwort

2

Sie müssen eine Dezimalbibliothek mit beliebiger Genauigkeit verwenden. (.Net 4.0 hat eine arbitrary integer class, aber nicht dezimal).

sind ein paar populäreren zur Verfügung:

18

Dies hat nichts mit der Genauigkeit der trigonometrischen Funktionen, sondern mehr mit dem CLS-System zu tun. Laut der Dokumentation hat eine double 15-16 Stellen Genauigkeit (was genau Sie erhalten), so dass Sie mit diesem Typ nicht genauer sein können. Wenn Sie also mehr Präzision wünschen, müssen Sie einen neuen Typ erstellen, der sie speichern kann.

Beachten Sie auch, dass Sie niemals einen Code wie folgt schreiben:

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

Sie sollten stattdessen tun:

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

Dies ist ein Ergebnis der Gleitkommagenauigkeit. Sie erhalten eine bestimmte Anzahl von signifikanten Ziffern, und alles, was nicht genau dargestellt werden kann, wird angenähert. Zum Beispiel ist Pi keine rationale Zahl, und daher ist es unmöglich, eine exakte Darstellung zu erhalten. Da du keinen exakten Wert von Pi erhalten kannst, wirst du keine genauen Sinus und Kosinus von Zahlen einschließlich Pi erhalten (noch wirst du die genauen Werte von Sinus und Kosinus die meiste Zeit erhalten). Die beste Zwischenerklärung ist "What Every Computer Scientist Should Know About Floating-Point Arithmetic". Wenn Sie nicht darauf eingehen wollen, denken Sie daran, dass Fließkommazahlen in der Regel Näherungen sind, und dass Fließkommaberechnungen wie das Verschieben von Sandhaufen auf dem Boden sind: Mit allem, was Sie damit machen, verlieren Sie ein wenig Sand und ein wenig Schmutz aufheben.

Wenn Sie eine exakte Darstellung wünschen, müssen Sie sich ein symbolisches Algebra-System suchen.

+0

Ich verstehe, wenn PI nicht genau wegen IEEE-754-Arithmetik definiert ist, und ich wünschte, ich könnte ein symbolisches Algebra-System mit C# fahren, aber ich kann jetzt nicht. – ja72

9

Ich höre dich. Ich bin sehr genervt von der Ungenauigkeit der Teilung. Neulich habe ich tat:

Console.WriteLine(1.0/3.0); 

und ich bekam ,333333333333333, statt der richtigen Antwort, die 0 ist.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333 ...

Vielleicht sehen Sie jetzt, was das Problem ist. Math.Pi ist nicht gleich Pi mehr als 1,0/3,0 ist gleich einem Drittel. Beide unterscheiden sich vom wahren Wert um ein paar Hundert Billiarden, und deshalb werden alle Berechnungen, die Sie mit Math.Pi oder 1.0/3.0 durchführen, auch um ein paar Hundert Quadrillionths abweichen, einschließlich des Sinus.

Wenn Sie nicht mögen, dass ungefähre Arithmetik ist ungefähre dann verwenden Sie nicht ungefähre Arithmetik. Verwenden Sie genaue Arithmetik. Ich benutzte Waterloo Maple, wenn ich genaue Arithmetik benötigte; Vielleicht sollten Sie eine Kopie davon kaufen.

+0

Aus Neugier, wenn man die exakte Arithmetik in C# berechnen möchte, wäre das möglich? Weiß ich einfach nicht, was man dazu benutzen kann? Nicht einmal Dezimalstellen würden es schneiden, oder? –

+2

@Joan: Ja, wir können es einfach für ganze Zahlen und sogar für rationale Zahlen machen (einfach Zähler/Nenner als große ganze Zahlen speichern) und Regeln in unsere Bibliothek werfen, egal welche reellen Zahlen wir wollen: pi, e, square- Wurzeln etc. Eine Bibliothek für Arithmetik beliebiger Genauigkeit an * jeder * vorstellbaren reellen Zahl ist jedoch unmöglich; selbst wenn man eine Möglichkeit hätte, sie zu speichern * (sagen wir als eine Formel, die uns eine beliebige Zahl der Zahl gibt) *, ist es rechnerisch unmöglich, sogar zwei beliebige reelle Zahlen für Gleichheit zu vergleichen !! –

+1

@BlueRaja: tatsächlich. Typischerweise werden Sie in diesem Fall die arithmetischen Größen symbolisch manipulieren; Sie haben ein Symbol für pi und ein Symbol für e, genauso wie Sie ein Symbol für 1, 2, 3 haben, und dann kodieren Sie alle arithmetischen und trigonometrischen Identitäten, so wie der Sinus von pi Null ist, und so weiter. Symbolische Mathematik ist schwer. –

1

ich die die Idee die Fehler aufgrund von Abrundungs ​​abzulehnen. Was getan werden kann ist sin(x) wie folgt zu definieren, eine Expansion des Taylor mit mit 6 Bedingungen:

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

Typische Fehler ist 1e-16 und schlimmste Fall ist 1e-11. Es ist schlechter als die CLR, aber es ist kontrollierbar durch Hinzufügen von mehr Termen. Die gute Nachricht ist, dass die Antwort für die Spezialfälle im OP und für genau ist.

+0

Related Post, auf dem Winkel genaue trig haben. http://math.stackexchange.com/q/176889/3301 – ja72

+0

Wenn das einzige Problem die speziellen Fälle des OP sind, könnte man sie auch als if-else-Anweisungen schreiben, anstatt mit einer solchen Lösung zu gehen. Diese Methoden können funktionieren, wenn sie direkt mit z.B. Null, aber kein ungefährer Wert. Wenn z.B. Wenn Sie Sin mit dem ungefähren Ergebnis einer vorherigen Operation aufrufen, wird es nicht funktionieren (z. B. weil x sehr klein ist, aber nicht genau Null - selbst wenn es "sein sollte"). Das wirkliche Problem hier ist die Verwendung von Doppel und die Erwartung, genaue Ergebnisse zu erhalten, die kein Algorithmus oder Formel in der Welt beheben kann. – enzi

Verwandte Themen