2016-04-26 4 views
3

Ich mache eine Konvertierung "Bytes [4] -> Gleitzahl -> Bytes [4]" ohne jede Arithmetik. In Bytes habe ich eine Nummer mit einfacher Genauigkeit im IEEE-754-Format (4 Bytes pro Nummer, Little-Endian-Reihenfolge wie in einer Maschine). Ich stoße auf ein Problem, wenn Bytes einen nicht wörtlich konvertierten NaN-Wert darstellen. Zum Beispiel:Konvertieren Float NaN-Werte von Binärform und umgekehrt führt zu einer Nichtübereinstimmung

{0x1B, 0xC4, 0xAB, 0x7F} -> NaN -> {0x1B, 0xC4, 0xEB, 0x7F}

-Code für die Wiedergabe:

using System; 
using System.Linq; 

namespace StrangeFloat 
{ 
    class Program 
    { 
     private static void PrintBytes(byte[] array) 
     { 
      foreach (byte b in array) 
      { 
       Console.Write("{0:X2}", b); 
      } 
      Console.WriteLine(); 
     } 

     static void Main(string[] args) 
     { 
      byte[] strangeFloat = { 0x1B, 0xC4, 0xAB, 0x7F }; 
      float[] array = new float[1]; 
      Buffer.BlockCopy(strangeFloat, 0, array, 0, 4); 
      byte[] bitConverterResult = BitConverter.GetBytes(array[0]); 

      PrintBytes(strangeFloat); 
      PrintBytes(bitConverterResult); 
      bool isEqual = strangeFloat.SequenceEqual(bitConverterResult); 
      Console.WriteLine("IsEqual: {0}", isEqual); 
     } 
    } 
} 

Ergebnis (https://ideone.com/p5fsrE):

1BC4AB7F 
1BC4EB7F 
IsEqual: False 

Dieses Verhalten hängt Von Plattform und Konfiguration: Dieser Code konvertiert eine Nummer ohne Fehler auf x64 in allen Konfigurationen oder in x86/Debug. Auf x86/Release ist ein Fehler aufgetreten.

Auch, wenn ich

ändern
byte[] bitConverterResult = BitConverter.GetBytes(array[0]); 

zu

float f = array[0]; 
byte[] bitConverterResult = BitConverter.GetBytes(f); 

dann erroneus auch auf x86/Debug.

ich tun um das Problem zu erforschen und festgestellt, dass Compiler x86-Code, der eine FPU Register verwenden (!) Zu einem halten einen Float-Wert (FLD/FST Anweisungen) erzeugen. Aber FPU setzt ein hohes Mantissen-Bit auf 1 statt auf 0, also ändert es den Wert, obwohl die Logik nur einen Wert ohne Änderung übergibt. Auf x64-Plattform ein xmm0 registrieren verwendet (SSE) und es funktioniert gut.

[Frage]

Was ist das: es ist ein irgendwo nicht definiertes Verhalten für einen NaN-Werte oder eineJIT/Optimierung Fehler dokumentiert?

Warum Compiler eine FPU und SSE verwenden, wenn keine arithmetischen Operationen vorgenommen wurden?

aktualisiert 1

Debug Konfiguration - Pass Wert über Stapel ohne Nebenwirkungen - korrektes Ergebnis:

byte[] bitConverterResult = BitConverter.GetBytes(array[0]); 
02232E45 mov   eax,dword ptr [ebp-44h] 
02232E48 cmp   dword ptr [eax+4],0 
02232E4C ja   02232E53 
02232E4E call  71EAC65A 
02232E53 push  dword ptr [eax+8] // eax+8 points to "1b c4 ab 7f" CORRECT! 
02232E56 call  7136D8E4 
02232E5B mov   dword ptr [ebp-5Ch],eax // eax points to managed 
// array data "fc 35 d7 70 04 00 00 00 __1b c4 ab 7f__" and this is correct 
02232E5E mov   eax,dword ptr [ebp-5Ch] 
02232E61 mov   dword ptr [ebp-48h],eax 

Veröffentlichung Konfiguration - Optimierer oder ein JIT tut einen seltsamen Pass über FPU registriert und bricht eine Daten - inkorrekt

byte[] bitConverterResult = BitConverter.GetBytes(array[0]); 
00B12DE8 cmp   dword ptr [edi+4],0 
00B12DEC jbe   00B12E3B 
00B12DEE fld   dword ptr [edi+8]  // edi+8 points to "1b c4 ab 7f" 
00B12DF1 fstp  dword ptr [ebp-10h] // ebp-10h points to "1b c4 eb 7f" (FAIL) 
00B12DF4 mov   ecx,dword ptr [ebp-10h] 
00B12DF7 call  70C75810 
00B12DFC mov   edi,eax 
00B12DFE mov   ecx,esi 
00B12E00 call  dword ptr ds:[4A70860h] 
+0

Es gibt mehrere Werte, die in der IEEE-Spezifikation für 'NaN' gelten. – leppie

+0

Erhalten Sie das gleiche Ergebnis mit Debug und Release? Ich glaube, dass Debug verwendet Software FPU zu simulieren, während Freigabe FPU in Computer verwenden. Wie alt ist der PC? Ich glaube, es gibt bekannte Probleme mit einigen UP-Gleitkomma-Einheiten. – jdweng

+2

Intel-Prozessorhandbuch: "Wenn einer oder beide Quelloperanden NaNs sind und die Gleitkommaoperation ungültig ist, wird die Operation maskiert, das Ergebnis ist in Tabelle 4-7 dargestellt. Wenn ein SNaN in ein QNaN konvertiert wird, wird die Konvertierung durchgeführt wird gehandhabt, indem ** das höchstwertige Bit des SNaN auf 1 gesetzt wird ** Auch wenn einer der Quelloperanden ein SNaN ist, wird das Flag für den ungültigen Betrieb des Gleitkommawerts gesetzt Es ist zu beachten, dass für einige Kombinationen von Quelloperanden, das Ergebnis ist unterschiedlich für x87 FPU Operationen und für SSE/SSE2/SSE3/SSE4.1 Operationen. Intel AVX folgt dem gleichen Verhalten als SSE/SSE2 ... " –

Antwort

1

Ich übersetze nur @HansPassant Kommentar als Antwort.

„Der x86-Jitter die FPU verwendet Gleitkommazahlen zu verarbeiten. Dies ist kein Fehler. Ihre Annahme, dass diese Bytewerte ein richtiges Argument für eine Methode, die einen Schwimmer Argument ist einfach falsch.“

Mit anderen Worten, dies ist nur ein GIGO Fall (Garbage in, garbage out).