Nein, dies ist keine weitere Frage "Warum ist (1 / 3.0) * 3! = 1" .
Ich habe in letzter Zeit viel über Gleitkomma gelesen; Insbesondere, wie dieselbe Berechnung auf verschiedenen Architekturen oder Optimierungseinstellungen zu unterschiedlichen Ergebnissen führen kann .
Dies ist ein Problem bei Videospielen, in denen Wiederholungen gespeichert sind oder die Peer-to-Peer-Netzwerke haben (im Gegensatz zu Server-Client), bei denen alle Clients bei jeder Ausführung des Programms genau dieselben Ergebnisse erzielen - eine kleine Diskrepanz in einem Gleitkommaberechnung kann auf verschiedenen Maschinen (oder sogar auf derselben Maschine! ) zu einem drastisch unterschiedlichen Spielzustand führen.
Dies geschieht sogar bei Prozessoren, die IEEE-754 "folgen" , hauptsächlich weil einige Prozessoren (nämlich x86) eine doppelt erweiterte Genauigkeit verwenden . Das heißt, sie verwenden 80-Bit-Register, um alle Berechnungen durchzuführen, und kürzen dann auf 64- oder 32-Bit, was zu anderen Rundungsergebnissen führt als Maschinen, die 64- oder 32-Bit für die Berechnungen verwenden.
Ich habe online mehrere Lösungen für dieses Problem gesehen, aber alle für C ++, nicht für C #:
- Deaktivieren Sie den Modus mit doppelter erweiterter Genauigkeit (damit alle
double
Berechnungen IEEE-754 64-Bit verwenden) mit_controlfp_s
(Windows),_FPU_SETCW
(Linux?) Oderfpsetprec
(BSD). - Führen Sie immer denselben Compiler mit denselben Optimierungseinstellungen aus und setzen Sie voraus, dass alle Benutzer dieselbe CPU-Architektur haben (kein plattformübergreifendes Spielen). Da mein "Compiler" eigentlich die JIT ist, die bei jeder Programmausführung möglicherweise anders optimiert wird , halte ich dies nicht für möglich.
- Verwenden Sie Festkomma-Arithmetik und vermeiden Sie
float
unddouble
insgesamt.decimal
würde für diesen Zweck funktionieren, wäre aber viel langsamer, und keine derSystem.Math
Bibliotheksfunktionen unterstützt dies.
Ist das überhaupt ein Problem in C #? Was ist, wenn ich nur Windows (nicht Mono) unterstützen möchte?
Wenn ja, gibt es eine Möglichkeit, mein Programm zu zwingen, mit normaler doppelter Genauigkeit ausgeführt zu werden?
Wenn nicht, gibt es Bibliotheken, die dazu beitragen , Gleitkommaberechnungen konsistent zu halten?
strictfp
Schlüsselwort, das alle Berechnungen in der angegebenen Größe (float
oderdouble
) anstelle einer erweiterten Größe erzwingt . Java hat jedoch immer noch viele Probleme mit der IEE-754-Unterstützung. Sehr (sehr, sehr) wenige Programmiersprachen unterstützen IEE-754 gut.Antworten:
Ich kenne keine Möglichkeit, normale Gleitkommawerte in .net deterministisch zu machen. Der JITter darf Code erstellen, der sich auf verschiedenen Plattformen (oder zwischen verschiedenen Versionen von .net) unterschiedlich verhält. Die Verwendung von normalem
float
s in deterministischem .net-Code ist daher nicht möglich.Die Problemumgehungen, die ich in Betracht gezogen habe:
Ich habe gerade eine Software-Implementierung von 32-Bit-Gleitkomma-Mathematik gestartet. Auf meinem 2,66 GHz i3 können ungefähr 70 Millionen Additionen / Multiplikationen pro Sekunde ausgeführt werden. https://github.com/CodesInChaos/SoftFloat . Offensichtlich ist es immer noch sehr unvollständig und fehlerhaft.
quelle
decimal
zuerst versuchen , da dies viel einfacher ist. Nur wenn es für die jeweilige Aufgabe zu langsam ist, sollten andere Ansätze in Betracht gezogen werden.Die C # -Spezifikation (§4.1.6 Gleitkommatypen) ermöglicht speziell Gleitkommaberechnungen mit einer Genauigkeit, die höher ist als die des Ergebnisses. Also, nein, ich glaube nicht, dass Sie diese Berechnungen direkt in .Net deterministisch machen können. Andere schlugen verschiedene Problemumgehungen vor, damit Sie sie ausprobieren können.
quelle
double
jedes Mal nach einer Operation die unerwünschten Bits entfernt werden, um konsistente Ergebnisse zu erzielen?Die folgende Seite kann hilfreich sein, wenn Sie eine absolute Portabilität solcher Vorgänge benötigen. Es wird Software zum Testen von Implementierungen des IEEE 754-Standards erörtert, einschließlich Software zum Emulieren von Gleitkommaoperationen. Die meisten Informationen sind jedoch wahrscheinlich spezifisch für C oder C ++.
http://www.math.utah.edu/~beebe/software/ieee/
Ein Hinweis zum Fixpunkt
Binäre Festkommazahlen können auch gut als Ersatz für Gleitkommazahlen dienen, wie aus den vier grundlegenden arithmetischen Operationen hervorgeht:
Binäre Festkommazahlen können für jeden ganzzahligen Datentyp wie int, long und BigInteger sowie für die nicht CLS-kompatiblen Typen uint und ulong implementiert werden.
Wie in einer anderen Antwort vorgeschlagen, können Sie Nachschlagetabellen verwenden, bei denen jedes Element in der Tabelle eine binäre Festkommazahl ist, um komplexe Funktionen wie Sinus, Cosinus, Quadratwurzel usw. zu implementieren. Wenn die Nachschlagetabelle weniger detailliert als die Festkommazahl ist, wird empfohlen, die Eingabe zu runden, indem Sie der Eingabe die Hälfte der Granularität der Nachschlagetabelle hinzufügen:
quelle
const
anstelle vonstatic
Konstanten, damit der Compiler sie optimieren kann. ziehe Mitgliedsfunktionen statischen Funktionen vor (damit wir z. B.myDouble.LeadingZeros()
statt aufrufen könnenIntDouble.LeadingZeros(myDouble)
); Versuchen Sie, Ein-Buchstaben-Variablennamen zu vermeiden (MultiplyAnyLength
z. B. hat 9, was es sehr schwierig macht, zu folgen)unchecked
und Nicht-CLS-kompatible Typen wieulong
,uint
etc. für die Geschwindigkeits Zwecke - weil sie so selten verwendet werden, wird die JIT sich nicht so aggressiv optimieren, so verwenden sie tatsächlich sein können langsamer normale Typen als die Verwendung von wielong
undint
. Außerdem weist C # eine Überladung des Bedieners auf , von der dieses Projekt stark profitieren würde. Gibt es schließlich zugehörige Unit-Tests? Abgesehen von diesen kleinen Dingen, tolle Arbeit, Peter, ist das lächerlich beeindruckend!strictfp
.Ist das ein Problem für C #?
Ja. Unterschiedliche Architekturen sind die geringste Sorge, unterschiedliche Frameraten usw. können zu Abweichungen aufgrund von Ungenauigkeiten in Float-Darstellungen führen - auch wenn sie dieselben Ungenauigkeiten aufweisen (z. B. dieselbe Architektur, außer einer langsameren GPU auf einem Computer).
Kann ich System.Decimal verwenden?
Es gibt keinen Grund, warum Sie nicht können, aber es ist Hund langsam.
Gibt es eine Möglichkeit, mein Programm zu zwingen, mit doppelter Genauigkeit ausgeführt zu werden?
Ja. Hosten Sie die CLR-Laufzeit selbst . und kompilieren Sie alle erforderlichen Aufrufe / Flags (die das Verhalten der Gleitkomma-Arithmetik ändern) in die C ++ - Anwendung, bevor Sie CorBindToRuntimeEx aufrufen.
Gibt es Bibliotheken, die dazu beitragen, Gleitkommaberechnungen konsistent zu halten?
Nicht, dass ich davon Wüste.
Gibt es einen anderen Weg, dies zu lösen?
Ich habe dieses Problem bereits zuvor angegangen . Die Idee ist, QNumbers zu verwenden . Sie sind eine Form von Realitäten, die Fixpunkte sind; aber kein fester Punkt in Basis-10 (dezimal) - eher Basis-2 (binär); Aus diesem Grund sind die mathematischen Grundelemente (add, sub, mul, div) viel schneller als die naiven Basis-10-Fixpunkte. vor allem, wenn
n
es für beide Werte gleich ist (was in Ihrem Fall der Fall wäre). Da sie ganzheitlich sind, erzielen sie auf jeder Plattform genau definierte Ergebnisse.Denken Sie daran, dass die Framerate diese weiterhin beeinflussen kann, aber nicht so schlecht ist und mithilfe von Synchronisationspunkten leicht korrigiert werden kann.
Kann ich mit QNumbers mehr mathematische Funktionen verwenden?
Ja, umrunden Sie dazu eine Dezimalstelle. Außerdem sollten Sie wirklich Nachschlagetabellen für die Triggerfunktionen (sin, cos) verwenden. wie kann wirklich unterschiedliche Ergebnisse auf verschiedenen Plattformen geben - und wenn man sie korrekt codieren können sie QNumbers direkt verwenden.
quelle
Laut diesem etwas alten MSDN-Blogeintrag wird die JIT SSE / SSE2 nicht als Gleitkomma verwenden, sondern nur x87. Aus diesem Grund müssen Sie sich, wie Sie bereits erwähnt haben, um Modi und Flags kümmern, und in C # ist dies nicht möglich. Die Verwendung normaler Gleitkommaoperationen garantiert daher nicht auf jedem Computer für Ihr Programm genau das gleiche Ergebnis.
Um eine präzise Reproduzierbarkeit mit doppelter Genauigkeit zu erhalten, müssen Sie eine Software-Gleitkomma- (oder Festkomma-) Emulation durchführen. Ich kenne keine C # -Bibliotheken, um dies zu tun.
Abhängig von den Operationen, die Sie benötigen, können Sie möglicherweise mit einfacher Präzision davonkommen. Hier ist die Idee:
Das große Problem bei x87 ist, dass Berechnungen mit einer Genauigkeit von 53 Bit oder 64 Bit durchgeführt werden können, abhängig vom Genauigkeitsflag und davon, ob das Register in den Speicher gelangt ist. Bei vielen Operationen garantiert die Ausführung der Operation mit hoher Präzision und das Zurückrunden auf eine niedrigere Präzision die richtige Antwort, was bedeutet, dass die Antwort auf allen Systemen gleich ist. Ob Sie die zusätzliche Präzision erhalten, spielt keine Rolle, da Sie über genügend Präzision verfügen, um in beiden Fällen die richtige Antwort zu gewährleisten.
Operationen, die in diesem Schema funktionieren sollten: Addition, Subtraktion, Multiplikation, Division, sqrt. Dinge wie Sünde, Erfahrung usw. funktionieren nicht (die Ergebnisse stimmen normalerweise überein, es gibt jedoch keine Garantie). "Wann ist Doppelrundung harmlos?" ACM-Referenz (bezahlte Reg.-Anforderung)
Hoffe das hilft!
quelle
Wie bereits in anderen Antworten angegeben: Ja, dies ist ein Problem in C # - auch wenn Sie reines Windows verwenden.
Als Lösung: Sie können das Problem vollständig reduzieren (und mit einigem Aufwand / Leistungseinbruch), wenn Sie eine integrierte
BigInteger
Klasse verwenden und alle Berechnungen auf eine definierte Genauigkeit skalieren, indem Sie einen gemeinsamen Nenner für die Berechnung / Speicherung solcher Zahlen verwenden.Wie von OP angefordert - in Bezug auf die Leistung:
System.Decimal
stellt eine Zahl mit 1 Bit für ein Vorzeichen und eine 96-Bit-Ganzzahl und eine "Skala" dar (die angibt, wo der Dezimalpunkt liegt). Für alle Berechnungen, die Sie durchführen, muss diese Datenstruktur verarbeitet werden und es können keine in die CPU integrierten Gleitkommaanweisungen verwendet werden.Die
BigInteger
"Lösung" macht etwas Ähnliches - nur dass Sie definieren können, wie viele Ziffern Sie benötigen / wollen ... vielleicht möchten Sie nur 80 Bit oder 240 Bit Genauigkeit.Die Langsamkeit ergibt sich immer daraus, dass alle Operationen mit dieser Nummer über Nur-Ganzzahl-Anweisungen simuliert werden müssen, ohne die in die CPU / FPU integrierten Anweisungen zu verwenden, was wiederum zu viel mehr Anweisungen pro mathematischer Operation führt.
Um den Leistungseinbruch zu verringern, gibt es verschiedene Strategien - wie QNumbers (siehe Antwort von Jonathan Dickinson - Ist Gleitkomma-Mathematik in C # konsistent? Kann es sein? ) Und / oder Caching (zum Beispiel Trigger-Berechnungen ...) usw.
quelle
BigInteger
nur in .Net 4.0 verfügbar ist.BigInteger
sogar den Performance-Hit von Decimal übertrifft.Decimal
(@Jonathan Dickinson - 'dog slow') oderBigInteger
(@CodeInChaos Kommentar oben) verwiesen. Kann jemand bitte eine kleine Erklärung zu diesen Leistungstreffern geben und ob / warum sie wirklich Show-Stopper für die Bereitstellung einer Lösung sind.Nun, hier wäre mein erster Versuch, wie das geht :
(Ich glaube, Sie können einfach zu einer 32-Bit-DLL kompilieren und diese dann entweder mit x86 oder AnyCpu verwenden [oder wahrscheinlich nur auf x86 auf einem 64-Bit-System abzielen; siehe Kommentar unten].)
Wenn es dann funktioniert, sollten Sie Mono verwenden möchten. Ich denke, Sie sollten in der Lage sein, die Bibliothek auf andere x86-Plattformen auf ähnliche Weise zu replizieren (natürlich nicht COM; obwohl vielleicht mit Wein? Ein wenig außerhalb meiner Region wir gehen aber dorthin ...).
Angenommen, Sie können es zum Laufen bringen, sollten Sie in der Lage sein, benutzerdefinierte Funktionen einzurichten, die mehrere Vorgänge gleichzeitig ausführen können, um Leistungsprobleme zu beheben, und Sie verfügen über Gleitkomma-Mathematik, mit der Sie plattformübergreifende konsistente Ergebnisse mit einer minimalen Menge erzielen können von Code, der in C ++ geschrieben wurde, und den Rest Ihres Codes in C # belassen.
quelle
x86
die 32-Bit-DLL laden.Ich bin kein Spieleentwickler, obwohl ich viel Erfahrung mit rechnerisch schwierigen Problemen habe ... also werde ich mein Bestes geben.
Die Strategie, die ich verfolgen würde, ist im Wesentlichen folgende:
Das kurze und lange ist: Sie müssen ein Gleichgewicht finden. Wenn Sie 30 ms rendern (~ 33 fps) und nur 1 ms Kollisionserkennung durchführen (oder eine andere hochempfindliche Operation einfügen) - selbst wenn Sie die Zeit für die kritische Arithmetik verdreifachen, hat dies Auswirkungen auf Ihre Framerate Sie fallen von 33,3 fps auf 30,3 fps.
Ich schlage vor, dass Sie alles profilieren, berücksichtigen, wie viel Zeit für jede der auffällig teuren Berechnungen aufgewendet wird, und dann die Messungen mit einer oder mehreren Methoden zur Lösung dieses Problems wiederholen und die Auswirkungen ermitteln.
quelle
Wenn Sie die Links in den anderen Antworten überprüfen, wird klar, dass Sie niemals eine Garantie dafür haben, ob Gleitkomma "korrekt" implementiert ist oder ob Sie für eine bestimmte Berechnung immer eine bestimmte Genauigkeit erhalten, aber vielleicht können Sie sich am besten darum bemühen (1) Abschneiden aller Berechnungen auf ein gemeinsames Minimum (z. B. wenn verschiedene Implementierungen eine Genauigkeit von 32 bis 80 Bit ergeben, wobei jede Operation immer auf 30 oder 31 Bit abgeschnitten wird), (2) beim Start eine Tabelle mit einigen Testfällen (Grenzfälle von Addieren, Subtrahieren, Multiplizieren, Dividieren, Quadrieren, Kosinus usw.) und wenn die Implementierung Werte berechnet, die mit der Tabelle übereinstimmen, müssen Sie keine Anpassungen vornehmen.
quelle
float
Datentyp auf x86-Computern. Dies führt jedoch zu geringfügig anderen Ergebnissen als bei Computern, die alle ihre Berechnungen mit nur 32 Bit ausführen, und diese kleinen Änderungen breiten sich im Laufe der Zeit aus. Daher die Frage.Deine Frage in ziemlich schwierigen und technischen Sachen O_o. Ich kann jedoch eine Idee haben.
Sie wissen sicher, dass die CPU nach schwebenden Vorgängen einige Anpassungen vornimmt. Und die CPU bietet verschiedene Anweisungen, die unterschiedliche Rundungsvorgänge ausführen.
Für einen Ausdruck wählt Ihr Compiler eine Reihe von Anweisungen aus, die Sie zu einem Ergebnis führen. Jeder andere Anweisungsworkflow kann jedoch ein anderes Ergebnis liefern, selbst wenn er denselben Ausdruck berechnen möchte.
Die 'Fehler', die durch eine Rundungsanpassung gemacht werden, wachsen mit jeder weiteren Anweisung.
Als Beispiel können wir sagen, dass auf Baugruppenebene: a * b * c nicht äquivalent zu a * c * b ist.
Da bin ich mir nicht ganz sicher, müssen Sie jemanden fragen, der die CPU-Architektur viel besser kennt als ich: p
Um Ihre Frage zu beantworten: In C oder C ++ können Sie Ihr Problem lösen, da Sie die Kontrolle über den von Ihrem Compiler generierten Maschinencode haben, in .NET jedoch keine. Solange Ihr Maschinencode unterschiedlich sein kann, sind Sie sich über das genaue Ergebnis nie sicher.
Ich bin gespannt, auf welche Weise dies ein Problem sein kann, da die Abweichung sehr gering erscheint. Wenn Sie jedoch eine wirklich genaue Operation benötigen, kann ich nur daran denken, die Größe Ihrer Floating-Register zu erhöhen. Verwenden Sie doppelte Genauigkeit oder sogar lange doppelte, wenn Sie können (nicht sicher, ob dies mit CLI möglich ist).
Ich hoffe ich war klar genug, ich bin nicht perfekt in Englisch (... überhaupt: s)
quelle