Ich habe diesen Code unter https://dotnetfiddle.net/ getestet :
using System;
public class Program
{
const float scale = 64 * 1024;
public static void Main()
{
Console.WriteLine(unchecked((uint)(ulong)(1.2 * scale * scale + 1.5 * scale)));
Console.WriteLine(unchecked((uint)(ulong)(scale* scale + 7)));
}
}
Wenn ich mit .NET 4.7.2 kompiliere, bekomme ich
859091763
7
Aber wenn ich Roslyn oder .NET Core mache, bekomme ich
859091763
0
Warum passiert das?
ulong
in wird im letzteren Fall ignoriert, sodass dies bei der Konvertierungfloat
-> geschiehtint
.ulong
auf das Ergebnis auswirkt.Antworten:
Meine Schlussfolgerungen waren falsch. Weitere Informationen finden Sie im Update.
Sieht aus wie ein Fehler im ersten Compiler, den Sie verwendet haben. Null ist in diesem Fall das richtige Ergebnis .Die durch die C # -Spezifikation vorgegebene Reihenfolge der Operationen ist wie folgt:scale
mitscale
, ergibta
a + 7
, nachgebenb
b
zuulong
, wodurch manc
c
zuuint
, wodurch mand
Bei den ersten beiden Operationen erhalten Sie einen Gleitkommawert von
b = 4.2949673E+09f
. Unter Standard-Gleitkomma-Arithmetik ist dies4294967296
( Sie können es hier überprüfen ). Das paßt inulong
ganz gut, soc = 4294967296
, aber es ist genau ein mehr alsuint.MaxValue
, so dass es Rundfahrten zu0
, daherd = 0
. Nun, was für eine Überraschung, da Gleitkommaarithmetik ist funky,4.2949673E+09f
und4.2949673E+09f + 7
ist genau die gleiche Anzahl in IEEE 754. Soscale * scale
gibt Ihnen den gleichen Wert einfloat
wiescale * scale + 7
,a = b
, so dass die zweite Operation ist im Grunde ein no-op.Der Roslyn-Compiler führt zur Kompilierungszeit (einige) const-Operationen aus und optimiert diesen gesamten Ausdruck auf
0
.Auch dies ist das richtige Ergebnis , undder Compiler darf alle Optimierungen durchführen, die genau das gleiche Verhalten wie der Code ohne sie ergeben.Ich vermute, dass der von Ihnen verwendete .NET 4.7.2-Compiler ebenfalls versucht, dies zu optimieren, aber einen Fehler aufweist, der dazu führt, dass die Besetzung an einer falschen Stelle ausgewertet wird. Natürlich, wenn Sie zuerst gegossenscale
zu einuint
und dann die Operation durchführen, erhalten Sie7
, dennscale * scale
Umläufe zu0
und dann Sie hinzufügen7
. Dies steht jedoch im Widerspruch zu dem Ergebnis, das Sie erhalten würden, wenn Sie die Ausdrücke zur Laufzeit Schritt für Schritt auswerten würden . Auch hier ist die Grundursache nur eine Vermutung, wenn man sich das erzeugte Verhalten ansieht, aber angesichts all dessen, was ich oben angegeben habe, bin ich überzeugt, dass dies eine Spezifikationsverletzung auf der Seite des ersten Compilers ist.AKTUALISIEREN:
Ich habe einen Trottel gemacht. Es gibt dieses Stück der C # -Spezifikation , von dem ich nicht wusste, dass es beim Schreiben der obigen Antwort existiert:
C # garantiert, dass Operationen mindestens auf der Ebene von IEEE 754 ein Maß an Präzision bieten , aber nicht unbedingt genau das. Es ist kein Fehler, es ist eine Spezifikationsfunktion. Der Roslyn Compiler ist in seinem Recht , den Ausdruck genau nach IEEE 754 angibt und der andere Compiler ist in ihrem rechten herzuleiten zu bewerten , die
2^32 + 7
sich ,7
wenn in setzenuint
.Es tut mir leid für meine irreführende erste Antwort, aber zumindest haben wir heute alle etwas gelernt.
quelle
scale
durch den Gleitkommawert ersetzen und dann zur Laufzeit alles andere auswerten würde, wäre das Ergebnis dasselbe. Können Sie das näher erläutern?Der Punkt hier ist (wie Sie in den Dokumenten sehen können ), dass Float- Werte nur eine Basis von bis zu 2 ^ 24 haben können . Wenn Sie also einen Wert von 2 ^ 32 zuweisen ( 64 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32 ), wird er tatsächlich 2 ^ 24 * 2 ^ 8 , das ist 4294967000 . Das Hinzufügen von 7 wird nur zu dem Teil hinzugefügt, der durch Konvertierung in ulong abgeschnitten wurde .
Wenn Sie zu double wechseln , das eine Basis von 2 ^ 53 hat , funktioniert es für das, was Sie wollen.
Dies kann ein Laufzeitproblem sein, in diesem Fall handelt es sich jedoch um ein Problem zur Kompilierungszeit, da alle Werte Konstanten sind und vom Compiler ausgewertet werden.
quelle
Zunächst verwenden Sie den ungeprüften Kontext, eine Anweisung für den Compiler. Als Entwickler sind Sie sicher, dass das Ergebnis nicht überläuft und Sie keinen Kompilierungsfehler sehen möchten. In Ihrem Szenario ist der Typ absichtlich überfüllt und Sie erwarten ein konsistentes Verhalten auf drei verschiedenen Compilern, von denen einer im Vergleich zu Roslyn und .NET Core, die neu sind, wahrscheinlich weit abwärtskompatibel zum Verlauf ist.
Zweitens mischen Sie implizite und explizite Konvertierungen. Ich bin mir über den Roslyn-Compiler nicht sicher, aber definitiv können .NET Framework- und .NET Core-Compiler unterschiedliche Optimierungen für diese Vorgänge verwenden.
Das Problem hierbei ist, dass in der ersten Zeile Ihres Codes nur Gleitkommawerte / -typen verwendet werden, in der zweiten Zeile jedoch eine Kombination aus Gleitkommawerten / -typen und Integralwert / -typ.
Wenn Sie sofort einen ganzzahligen Gleitkommatyp (7> 7.0) erstellen, erhalten Sie für alle drei kompilierten Quellen das gleiche Ergebnis.
Ich würde also das Gegenteil von der Antwort von V0ldek sagen und das ist "Der Fehler (wenn es wirklich ein Fehler ist) ist höchstwahrscheinlich in Roslyn- und .NET Core-Compilern".
Ein weiterer Grund zu der Annahme, dass das Ergebnis der ersten ungeprüften Berechnungsergebnisse für alle gleich ist und der Wert den Maximalwert des
UInt32
Typs überschreitet .Minus eins ist da, wenn wir bei Null beginnen, was ein Wert ist, der sich nur schwer selbst subtrahieren lässt. Wenn mein mathematisches Verständnis des Überlaufs korrekt ist, beginnen wir mit der nächsten Zahl nach dem Maximalwert.
AKTUALISIEREN
Nach dem Jalsh-Kommentar
Sein Kommentar ist richtig. Wenn wir float verwenden, erhalten Sie immer noch 0 für Roslyn und .NET Core, andererseits verwenden Sie doppelte Ergebnisse in 7.
Ich habe einige zusätzliche Tests gemacht und die Dinge werden noch seltsamer, aber am Ende macht alles Sinn (zumindest ein bisschen).
Ich gehe davon aus, dass der .NET Framework 4.7.2-Compiler (veröffentlicht Mitte 2018) wirklich andere Optimierungen verwendet als die .NET Core 3.1- und Roslyn 3.4-Compiler (veröffentlicht Ende 2019). Diese verschiedenen Optimierungen / Berechnungen werden ausschließlich für konstante Werte verwendet, die zur Kompilierungszeit bekannt sind. Aus diesem Grund musste das
unchecked
Schlüsselwort verwendet werden, da der Compiler bereits weiß, dass ein Überlauf auftritt. Zur Optimierung der endgültigen IL wurden jedoch andere Berechnungen verwendet.Gleicher Quellcode und fast dieselbe IL mit Ausnahme der Anweisung IL_000a. Ein Compiler berechnet 7 und der andere 0.
Quellcode
.NET Framework (x64) IL
Roslyn-Compiler-Zweig (September 2019) IL
Es beginnt den richtigen Weg zu gehen, wenn Sie nicht konstante Ausdrücke (standardmäßig
unchecked
) wie unten hinzufügen .Was von beiden Compilern "genau" dieselbe IL erzeugt.
.NET Framework (x64) IL
Roslyn-Compiler-Zweig (September 2019) IL
Letztendlich glaube ich, dass der Grund für das unterschiedliche Verhalten nur eine andere Version des Frameworks und / oder Compilers ist, die unterschiedliche Optimierungen / Berechnungen für konstante Ausdrücke verwenden, aber in anderen Fällen ist das Verhalten sehr gleich.
quelle