[TestMethod] public void CheckDF() { // Float var bytes = new byte[4]; var random = new Random(); for (int i = 0; i < 5000000; i++) { random.NextBytes(bytes); // Convert bytes to float. float float_from_bytes = BitConverter.ToSingle(bytes, 0); // Convert float to bytes. var bytes_from_float = BitConverter.GetBytes(float_from_bytes); if (!bytes.SequenceEqual(bytes_from_float)) { Assert.IsTrue(Single.IsNaN(float_from_bytes)); float[] floats = new float[1]; Buffer.BlockCopy(bytes, 0, floats, 0, 4); Assert.IsTrue(Single.IsNaN(floats[0])); byte[] bytes_from_floats = new byte[4]; Buffer.BlockCopy(floats, 0, bytes_from_floats, 0, 4); CollectionAssert.AreEqual(bytes, bytes_from_floats); Assert.IsFalse(float_from_bytes == floats[0]); float f1 = floats[0]; Assert.IsTrue(Single.IsNaN(f1)); Assert.IsTrue(f1 != float_from_bytes); Assert.IsTrue(f1 != floats[0]); Assert.IsTrue(float_from_bytes != floats[0]); var bytes1 = BitConverter.GetBytes(f1); CollectionAssert.AreNotEqual(bytes, bytes1); CollectionAssert.AreNotEqual(bytes_from_floats, bytes1); float ff1 = f1 + 0.0f; float ff2 = float_from_bytes + 0.0f; float ff3 = floats[0] + 0.0f; Assert.IsTrue(Single.IsNaN(ff1)); Assert.IsTrue(Single.IsNaN(ff2)); Assert.IsTrue(Single.IsNaN(ff3)); byte[] bb1 = BitConverter.GetBytes(ff1); byte[] bb2 = BitConverter.GetBytes(ff2); byte[] bb3 = BitConverter.GetBytes(ff3); CollectionAssert.AreEqual(bb1, bb2); CollectionAssert.AreEqual(bb2, bb3); CollectionAssert.AreEqual(bytes1, bb1); } } }
Istnieje wiele kombinacji bajtów, które tworzą liczbę typu
NaN
i ta liczba jeśli tylko przejdzie przez rejestr FPU jest zmieniana na jedyną poprawną liczbę NaN. Przykładowo dzieje się tak w linii 35. Podobnie w 47, 48, 49. Mam system x64. Problem ten występuje zarówno w wersji
Release
jak i Debug
podczas kompilacji do x86. W x64 w linii 35 liczba nie przechodzi przez FPU, więc nie jest ona zmieniana. Ale oczywiście samo dodawanie poprawi ją.Przy okazji nigdzie nie porównuje liczb
NaN
ze sobą, gdyż takie porównanie zawsze zwróci false
.Podobnie dzieje się w przypadku liczb
double
.Wniosek: tworzenie liczb zmiennoprzecinkowych z losowych sekwencji bajtów nie jest dobrym pomysłem, zwłaszcza jeśli wynikiem są liczby
NaN
. Ja tak niestety robiłem i ostatnio się zdziwiłem jak kod testowy algorytmów hashujących, które przyjmują prawie zawsze sekwencję bajtów na wyjście nagle zaczął losowo zwracać błędy.
No i najlepsze ale ciężko mi to potwierdzić. Teraz zaczął zawodzić prawie każdy test (sekwencje bajtów są losowane). Poprzednio, a testy nie zmieniły się może od pół roku nigdy nie było problemów. Ciężko mi to potwierdzić czy coś nie zmieniło się w kompilatorze JIT, może w samej platformie .NET.
No aby tytułowi stało się zadość w kodzie widzimy dwie możliwości jak konwertować pomiędzy tablicą bajtów, a liczbą zmiennoprzecinkową. Mamy do dyspozycji dla pojedynczych liczb
BitConverter
oraz bardziej właściwy dla tablic Buffer.BlockCopy
.