2010-10-09

Konwersja pomiędzy double, float, bytes

Kod:

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

Brak komentarzy:

Prześlij komentarz