Ist Gleitkomma-Mathematik in C # konsistent? Kann es sein?


155

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 doubleBerechnungen IEEE-754 64-Bit verwenden) mit _controlfp_s(Windows), _FPU_SETCW(Linux?) Oder fpsetprec(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 floatund doubleinsgesamt. decimalwürde für diesen Zweck funktionieren, wäre aber viel langsamer, und keine der System.MathBibliotheksfunktionen 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?


Ich habe diese Frage gesehen , aber jede Antwort wiederholt entweder das Problem ohne Lösung oder sagt "ignoriere es", was keine Option ist. Ich habe eine ähnliche Frage zu gamedev gestellt , aber (wegen des Publikums) scheinen die meisten Antworten auf C ++ ausgerichtet zu sein.
BlueRaja - Danny Pflughoeft

1
Keine Antwort, aber ich bin sicher, dass Sie in den meisten Bereichen Ihr System so gestalten können, dass der gesamte gemeinsam genutzte Status deterministisch ist und es aus diesem
Grund

1
@Peter kennen Sie eine schnelle Gleitkomma-Emulation für .net?
CodesInChaos

1
Leidet Java unter diesem Problem?
Josh

3
@Josh: Java hat das strictfpSchlüsselwort, das alle Berechnungen in der angegebenen Größe ( floatoder double) 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.
porges

Antworten:


52

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 floats in deterministischem .net-Code ist daher nicht möglich.

Die Problemumgehungen, die ich in Betracht gezogen habe:

  1. Implementieren Sie FixedPoint32 in C #. Dies ist zwar nicht allzu schwierig (ich habe eine halbfertige Implementierung), aber der sehr kleine Wertebereich macht die Verwendung ärgerlich. Sie müssen immer vorsichtig sein, damit Sie weder überlaufen noch zu viel Präzision verlieren. Am Ende fand ich das nicht einfacher als die direkte Verwendung von ganzen Zahlen.
  2. Implementieren Sie FixedPoint64 in C #. Ich fand das ziemlich schwierig. Für einige Operationen wären Zwischenzahlen von 128 Bit nützlich. Aber .net bietet einen solchen Typ nicht an.
  3. Implementieren Sie einen benutzerdefinierten 32-Bit-Gleitkomma. Das Fehlen einer BitScanReverse-Eigenschaft verursacht bei der Implementierung einige Probleme. Aber momentan denke ich, dass dies der vielversprechendste Weg ist.
  4. Verwenden Sie nativen Code für die mathematischen Operationen. Entsteht bei jeder mathematischen Operation der Aufwand eines Delegatenaufrufs.

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.


2
Es gibt eine "unbegrenzte" Ganzzahl, die BigInteger enthält, obwohl sie nicht so schnell wie native int oder lang ist. Daher bietet .NET einen solchen Typ an (erstellt für F #, glaube ich, kann aber in C # verwendet werden)
Rune FS

Eine weitere Option ist der GNU MP-Wrapper für .NET . Es ist ein Wrapper um die GNU Multiple Precision Library, der "unendliche" Präzisions-Ganzzahlen, Rationals (Brüche) und Gleitkommazahlen unterstützt.
Cole Johnson

2
Wenn Sie eines dieser Verfahren ausführen möchten, können Sie es auch decimalzuerst 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.
Roman Starkov

Ich habe einen Sonderfall kennengelernt, bei dem Gleitkommawerte deterministisch sind. Die Erklärung, die ich erhalten habe, lautet: Wenn für die Multiplikation / Division eine der FP-Zahlen die Zweierpotenz (2 ^ x) ist, ändert sich die Signifikanz / Mantisse während der Berechnung nicht. Nur der Exponent ändert sich (der Punkt bewegt sich). Eine Rundung wird also niemals stattfinden. Das Ergebnis wird deterministisch sein.
Zickzack

Beispiel: Eine Zahl wie 2 ^ 32 wird dargestellt als (Exponent: 32, Mantisse: 1). Wenn wir dies mit einem anderen Float (exp, man) multiplizieren, ist das Ergebnis (exp + 32, man * 1). Für die Division ist das Ergebnis (expo - 32, man * 1). Das Multiplizieren der Mantisse mit 1 ändert die Mantisse nicht, es spielt also keine Rolle, wie viele Bits sie hat.
Zickzack

28

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.


9
Ich habe gerade festgestellt, dass die C # -Spezifikation keine Rolle spielt, wenn man kompilierte Assemblys verteilt. Es ist nur wichtig, wenn man Quellkompatibilität will. Was wirklich zählt, ist die CLR-Spezifikation. Aber ich bin mir ziemlich sicher, dass die Garantien genauso schwach sind wie die C # -Garantien.
CodesInChaos

Würde nicht doublejedes Mal nach einer Operation die unerwünschten Bits entfernt werden, um konsistente Ergebnisse zu erzielen?
IllidanS4 will Monica

2
@ IllidanS4 Ich glaube nicht, dass dies konsistente Ergebnisse garantieren würde.
Svick

14

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:

  • Addition und Subtraktion sind trivial. Sie funktionieren genauso wie ganze Zahlen. Einfach addieren oder subtrahieren!
  • Um zwei Festkommazahlen zu multiplizieren, multiplizieren Sie die beiden Zahlen und verschieben Sie dann die definierte Anzahl von Bruchbits nach rechts.
  • Um zwei Festkommazahlen zu teilen, verschieben Sie die Dividende um die definierte Anzahl von Bruchbits nach links und dividieren Sie sie durch den Divisor.
  • Kapitel 4 dieses Dokuments enthält zusätzliche Anleitungen zur Implementierung von binären Festkommazahlen.

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:

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];

5
Sie sollten dies auf eine Open-Source-Code-Projektwebsite wie Sourceforge oder Github hochladen. Dies erleichtert das Auffinden, das Hinzufügen von Beiträgen, das Einfügen in Ihren Lebenslauf usw. Außerdem einige Tipps zum Quellcode (Sie können sie gerne ignorieren): Verwenden Sie constanstelle von staticKonstanten, damit der Compiler sie optimieren kann. ziehe Mitgliedsfunktionen statischen Funktionen vor (damit wir z. B. myDouble.LeadingZeros()statt aufrufen können IntDouble.LeadingZeros(myDouble)); Versuchen Sie, Ein-Buchstaben-Variablennamen zu vermeiden ( MultiplyAnyLengthz. B. hat 9, was es sehr schwierig macht, zu folgen)
BlueRaja - Danny Pflughoeft

Seien Sie vorsichtig mit uncheckedund Nicht-CLS-kompatible Typen wie ulong, uintetc. 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 wie longund int. 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!
BlueRaja - Danny Pflughoeft

Vielen Dank für die Kommentare. Ich führe Unit-Tests mit dem Code durch. Sie sind jedoch ziemlich umfangreich, viel zu umfangreich, um sie vorerst zu veröffentlichen. Ich schreibe sogar Hilfsroutinen für Unit-Tests, um das Schreiben mehrerer Tests zu vereinfachen. Ich verwende derzeit keine überladenen Operatoren, da ich vorhabe, den Code nach Abschluss des Vorgangs in Java zu übersetzen.
Peter O.

2
Das Lustige ist, als ich auf Ihrem Blog gepostet habe, habe ich nicht bemerkt, dass Ihr Blog Ihnen gehört. Ich hatte mich gerade für Google + entschieden und in seinem C # -Funken schlug es diesen Blogeintrag vor. Also dachte ich: "Was für ein bemerkenswerter Zufall für uns beide, gleichzeitig so etwas zu schreiben." Aber natürlich hatten wir den gleichen Auslöser :)
CodesInChaos

1
Warum sollte man sich die Mühe machen, dies nach Java zu portieren? Java hat bereits deterministische Gleitkomma-Mathematik über garantiert strictfp.
Antimon

9

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 nes 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.


3
Ich bin mir nicht sicher, wovon Sie sprechen, wenn es um Frameraten geht. Natürlich möchten Sie eine feste Aktualisierungsrate (siehe zum Beispiel hier ) - ob dies mit der Anzeige-Framerate übereinstimmt oder nicht, spielt keine Rolle. Solange die Ungenauigkeiten auf allen Maschinen gleich sind, sind wir gut. Ich verstehe deine dritte Antwort überhaupt nicht.
BlueRaja - Danny Pflughoeft

@BlueRaja: Die Antwort "Gibt es eine Möglichkeit, mein Programm zu zwingen, mit doppelter Genauigkeit ausgeführt zu werden?" Dies würde entweder bedeuten, dass die gesamte Common Language Runtime erneut implementiert wird, was äußerst kompliziert wäre, oder native Aufrufe einer C ++ - DLL aus der C # -Anwendung verwenden, wie in der Antwort von Benutzer shelleybutterfly angedeutet. Stellen Sie sich "QNumbers" lediglich als binäre Festkommazahlen vor, wie in meiner Antwort angedeutet (ich hatte bisher noch nicht gesehen, dass binäre Festkommazahlen "QNumbers" genannt werden.)
Peter O.

@Pieter O. Sie müssen die Laufzeit nicht erneut implementieren. Der Server, auf dem ich in meinem Unternehmen arbeite, hostet die CLR-Laufzeit als native C ++ - Anwendung (SQL Server auch). Ich schlage vor, Sie googeln CorBindToRuntimeEx.
Jonathan Dickinson

@ BlueRaja es kommt auf das jeweilige Spiel an. Das Anwenden von Schritten mit fester Framerate auf alle Spiele ist keine praktikable Option, da der AOE-Algorithmus eine künstliche Latenz einführt. was in zB einem FPS nicht akzeptabel ist.
Jonathan Dickinson

1
@ Jonathan: Dies ist nur ein Problem in Peer-to-Peer - Spielen , die nur den Eingang senden - für diese, Sie haben eine feste Update-Rate haben. Die meisten FPS funktionieren nicht so, aber die wenigen, die notwendigerweise eine feste Aktualisierungsrate haben. Siehe diese Frage .
BlueRaja - Danny Pflughoeft

6

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:

  • Speichern Sie alle Werte, die Ihnen wichtig sind, mit einer einzigen Genauigkeit
  • um eine Operation durchzuführen:
    • Erweitern Sie Eingaben auf doppelte Genauigkeit
    • Arbeiten Sie mit doppelter Präzision
    • Konvertieren Sie das Ergebnis zurück in einfache Genauigkeit

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!


2
Es ist auch ein Problem, dass .NET 5, 6 oder 42 den x87-Berechnungsmodus möglicherweise nicht mehr verwenden. Der Standard enthält nichts, was dies erfordert.
Eric J.

5

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 BigIntegerKlasse 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.Decimalstellt 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.


1
Beachten Sie, dass dies BigIntegernur in .Net 4.0 verfügbar ist.
Svick

Ich vermute, dass der Performance-Hit von BigIntegersogar den Performance-Hit von Decimal übertrifft.
CodesInChaos

Ein paar Mal in den Antworten hier wird auf den Leistungseinbruch bei der Verwendung von Decimal(@Jonathan Dickinson - 'dog slow') oder BigInteger(@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.
Barry Kaye

@Yahia - danke für die Bearbeitung - interessante Lektüre, könnten Sie bitte auch eine Ball-Park-Schätzung bezüglich des Performance-Hits geben, wenn Sie 'float' nicht verwenden? Sprechen wir 10% langsamer oder 10-mal langsamer - ich nur Ich möchte ein Gefühl für die implizierte Größenordnung bekommen.
Barry Kaye

es ist eher im Bereich von 1: 5 als "nur 10%"
Yahia

2

Nun, hier wäre mein erster Versuch, wie das geht :

  1. Erstellen Sie ein ATL.dll-Projekt mit einem einfachen Objekt, das für Ihre kritischen Gleitkommaoperationen verwendet werden soll. Stellen Sie sicher, dass Sie es mit Flags kompilieren, die die Verwendung von Nicht-xx87-Hardware für Gleitkommazahlen deaktivieren.
  2. Erstellen Sie Funktionen, die Gleitkommaoperationen aufrufen und die Ergebnisse zurückgeben. Fangen Sie einfach an und wenn es für Sie funktioniert, können Sie die Komplexität jederzeit erhöhen, um Ihre Leistungsanforderungen später bei Bedarf zu erfüllen.
  3. Setzen Sie die control_fp-Aufrufe um die eigentliche Mathematik, um sicherzustellen, dass dies auf allen Computern auf die gleiche Weise erfolgt.
  4. Verweisen Sie auf Ihre neue Bibliothek und testen Sie, ob sie wie erwartet funktioniert.

(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.


"In eine 32-Bit-DLL kompilieren und dann ... AnyCpu verwenden" Ich denke, dies funktioniert nur, wenn es auf einem 32-Bit-System ausgeführt wird. Auf einem 64-Bit-System kann nur ein Programmziel x86die 32-Bit-DLL laden.
CodesInChaos

2

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:

  • Verwenden Sie eine langsamere (falls erforderlich; wenn es einen schnelleren Weg gibt, großartig!), Aber vorhersehbare Methode, um reproduzierbare Ergebnisse zu erzielen
  • Verwenden Sie double für alles andere (z. B. Rendern).

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.


1

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.


Jede Operation wird immer auf 30 oder 31 Bit gekürzt - genau das macht der floatDatentyp 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.
BlueRaja - Danny Pflughoeft

Wenn "N Bits Genauigkeit" bedeutet, dass eine Berechnung auf so viele Bits genau ist und Maschine A auf 32 Bits genau ist, während Maschine B auf 48 Bits genau ist, sollten die ersten 32 Bits einer Berechnung durch beide Maschinen identisch sein. Würde das Abschneiden auf 32 Bit oder weniger nach jedem Vorgang nicht beide Maschinen exakt synchron halten? Wenn nicht, was ist ein Beispiel?
Zeugenschutz ID 44583292

-3

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)


9
Stellen Sie sich einen P2P-Shooter vor. Du schießt auf einen Kerl, du schlägst ihn und er stirbt, aber es ist sehr nah, du hast es fast verpasst. Auf dem PC des anderen Mannes werden etwas andere Berechnungen verwendet und es wird berechnet, dass Sie vermissen. Sehen Sie das Problem jetzt? In diesem Fall hilft eine Vergrößerung der Register nicht (zumindest nicht vollständig). Verwenden Sie auf jedem Computer genau die gleiche Berechnung.
Svick

5
In diesem Szenario ist es normalerweise egal, wie nahe das Ergebnis am tatsächlichen Ergebnis liegt (solange es vernünftig ist), aber was zählt, ist, dass es für alle Benutzer genau gleich ist.
CodesInChaos

1
Sie haben Recht, ich habe nicht über diese Art von Szenario nachgedacht. Ich stimme jedoch @CodeInChaos in diesem Punkt zu. Ich fand es nicht wirklich klug, zweimal eine wichtige Entscheidung zu treffen. Dies ist eher ein Problem mit der Softwarearchitektur. Ein Programm, zum Beispiel die Anwendung des Schützen, sollte die Berechnung durchführen und das Ergebnis an die anderen senden. Sie werden auf diese Weise niemals Fehler haben. Sie haben einen Treffer oder nicht, aber nur einer trifft die Entscheidung. Wie sagen @driushkin
AxFab

2
@Aesgar: Ja, so arbeiten die meisten Schützen. Diese "Autorität" wird als Server bezeichnet, und wir nennen die Gesamtarchitektur eine "Client / Server" -Architektur. Es gibt jedoch eine andere Art von Architektur: Peer-to-Peer. In P2P gibt es keinen Server. Stattdessen müssen alle Clients alle Aktionen miteinander überprüfen, bevor etwas passiert. Dies erhöht die Verzögerung und macht sie für Schützen nicht akzeptabel, verringert jedoch den Netzwerkverkehr erheblich. Dies macht sie perfekt für Spiele, bei denen eine kleine Verzögerung (~ 250 ms) akzeptabel ist, die Synchronisierung des gesamten Spielstatus jedoch nicht. RTS-Spiele wie C & C und Starcraft verwenden nämlich P2P.
BlueRaja - Danny Pflughoeft

5
In einem P2P-Spiel gibt es keinen vertrauenswürdigen Computer, auf den Sie sich verlassen können. Wenn Sie einer Station erlauben, zu entscheiden, ob ihre Kugel getroffen wurde oder nicht, eröffnen Sie die Möglichkeit, dass ein Kunde betrügt. Darüber hinaus können die Links nicht einmal die Datenmenge verarbeiten, die manchmal resultiert - die Spiele senden die Bestellungen und nicht die Ergebnisse. Ich spiele RTS-Spiele und oft habe ich so viel Müll herumfliegen sehen, dass es unmöglich ist, ihn über normale Haushalts-Uplinks zu senden.
Loren Pechtel
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.