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.

2010-10-06

Mutex with FIFO order and CancellationToken support

This is simple mutex implementation which releasing threads in FIFO manner. Normally it's race. Its also support CancellationToken.

public class QueuedMutex
{
    private Object m_lock = new Object();
    private Queue m_queue = new Queue();
    private bool m_firstGo = false;

    public void WaitOne(CancellationToken a_token)
    {
        ManualResetEvent mre = null;

        lock (m_lock)
        {
            if (m_firstGo)
            {
                mre = new ManualResetEvent(false);
                m_queue.Enqueue(mre);
            }
            else
                m_firstGo = true;
        }

        if (mre != null)
        {
            while (!mre.WaitOne(100))
            {
                if (a_token.IsCancellationRequested)
                {
                    lock (m_lock)
                    {
                        if (mre.WaitOne(0))
                        {
                            ReleaseMutex();
                        }
                        else if (m_queue.Contains(mre))
                        {
                            List list = new List();
                            while (m_queue.Peek() != mre)
                                list.Add(m_queue.Dequeue());
                            m_queue.Dequeue();
                            while (list.Count != 0)
                            {
                                m_queue.Enqueue(list.Last());
                                list.RemoveLast();
                            }
                        }
                    }
                    a_token.ThrowIfCancellationRequested();
                }
            }
        }
    }

    public void ReleaseMutex()
    {
        lock (m_lock)
        {
            if (m_queue.Count != 0)
                m_queue.Dequeue().Set();

            if (m_queue.Count == 0)
                m_firstGo = false;
        }
    }
}

ReadOnlyDictionary

This is simple implementation of read only Dictionary:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ReadOnlyDictionaryLib
{
    public static class DictionaryExtensions
    {
        public static IDictionary<TKey, TValue> AsReadOnlyx<TKey, TValue>(this IDictionary<TKey, TValue> a_source)
        {
            return new ReadOnlyDictionary<TKey, TValue>(a_source);
        }
    }

    public sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        private IDictionary<TKey, TValue> m_dictionary;

        public ReadOnlyDictionary(IDictionary<TKey, TValue> a_source)
        {
            m_dictionary = a_source;
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (var item in m_dictionary)
            {
                yield return item;
            }
        }

        public bool ContainsKey(TKey a_key)
        {
            return m_dictionary.ContainsKey(a_key);
        }

        public bool TryGetValue(TKey a_key, out TValue a_value)
        {
            return m_dictionary.TryGetValue(a_key, out a_value);
        }

        public bool Contains(KeyValuePair<TKey, TValue> a_item)
        {
            return m_dictionary.Contains(a_item);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] a_array, int a_arrayIndex)
        {
            m_dictionary.CopyTo(a_array, a_arrayIndex);
        }

        public TValue this[TKey a_key]
        {
            get
            {
                return m_dictionary[a_key];
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                return new ReadOnlyCollection<TKey>(new List<TKey>(m_dictionary.Keys));
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                return new ReadOnlyCollection<TValue>(new List<TValue>(m_dictionary.Values));
            }
        }

        public int Count
        {
            get
            {
                return m_dictionary.Count;
            }
        }

        public bool IsReadOnly
        {
            get 
            { 
                return true; 
            }
        }

        void IDictionary<TKey, TValue>.Add(TKey a_key, TValue a_value)
        {
            throw new NotSupportedException();
        }

        bool IDictionary<TKey, TValue>.Remove(TKey a_key)
        {
            throw new NotSupportedException();
        }

        TValue IDictionary<TKey, TValue>.this[TKey a_key]
        {
            get
            {
                return this[a_key];
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> a_item)
        {
            throw new NotSupportedException();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> a_item)
        {
            throw new NotSupportedException();
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            throw new NotSupportedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}