Dev Blog

IEEE стандарт в c#

Рассмотрим представление для типа float. Этот тип имеет 4 байта или 32 бита.

Он состоит из бита знака, 8 битов экспоненты, 23 бита мантиссы

sxxx xxxx xmmm mmmm mmmm mmmm mmmm mmmm, 

s - знак 0 - плюс, 1 минус

x - экспонента, для числа вычисляется как 2([десятичное представление x] - 127)

m - мантисса числа вычисляется из двоичного 1.[биты мантиссы], то есть оно больше или равно 1 и меньше 2

Итого число вычисляется так: ([бит знака] ? -1  : 1) * (1 + [мантисса после точки]) * 2([экспонента] - 127)

 

Попробуем сопоставить некоторые значения с битовым представление. Для этого нужна вспомогательная структура:

[StructLayout(LayoutKind.Explicit)]
internal struct Complex
{
    [FieldOffset(0)]
    public float Float;

    [FieldOffset(0)]
    public uint Uint;
}

Задавая число Float можно будет посмотреть биты через Uint и наоборот.

public class IEEETests
{
    [Theory]
    //             sxxx xxxx xmmm mmmm mmmm mmmm mmmm mmmm
    [InlineData(0b_0011_1111_1000_0000_0000_0000_0000_0000, 1)] // x = 127, 1 * 2^(127 - 127)
    [InlineData(0b_0100_0000_0000_0000_0000_0000_0000_0000, 2)] // x = 128, 1 * 2^(128 - 127)
    [InlineData(0b_0100_0000_1000_0000_0000_0000_0000_0000, 4)] // x = 129, 1 * 2^(129 - 127)
    [InlineData(0b_0100_0000_1100_0000_0000_0000_0000_0000, 6)] // x = 129, 1.5 * 2^(129 - 127)   (1.5(10) = 1.1(2))
    [InlineData(0b_0000_0000_0000_0000_0000_0000_0000_0000, 0)] // zero
    [InlineData(0b_1000_0000_0000_0000_0000_0000_0000_0000, 0)] // negative zero
    [InlineData(0b_1111_1111_1100_0000_0000_0000_0000_0000, float.NaN)] // signal value for hardware error
    [InlineData(0b_0111_1111_1100_0000_0000_0000_0000_0000, float.NaN)] // non signal for math errors
    [InlineData(0b_0111_1111_1110_0000_0000_0000_0000_0000, float.NaN)]
    [InlineData(0b_1111_1111_1111_0000_0000_0000_0000_0000, float.NaN)]
    [InlineData(0b_0111_1111_1000_0000_0000_0000_0000_0000, float.PositiveInfinity)]
    [InlineData(0b_1111_1111_1000_0000_0000_0000_0000_0000, float.NegativeInfinity)]
    [InlineData(0b_0111_1111_0111_1111_1111_1111_1111_1111, float.MaxValue)] //3,4028235E+38 = 1,99999988079071044921875 * 2^(254 - 127) 
    [InlineData(0b_1111_1111_0111_1111_1111_1111_1111_1111, float.MinValue)] //-3,4028235E+38 = -1 * 1,99999988079071044921875 * 2^(254 - 127)
    public void BitFloatTest(uint bits, float actual)
    {
        var c = new Complex
        {
            Uint = bits
        };

        Assert.Equal(actual, c.Float);
    }
}

Как видно из теста есть специальные значения:

Ноль это все нули с любым знаком.

"Не число" это экспонента из всех единиц и ненулевой мантиссы. Причем есть сигнальный и не сигнальный вариант, это говорит о ошибке либо в железе либо в вычислениях соответственно. Сигнальность определяется знаком. 1 - сигнальное значение.

Плюс бесконечность и минус бесконечность это экспонента из единичек, мантисса из нулей и знак.

Максимальное значение и минимальное это знак, экспонента из всех единиц кроме последней и мантисса из единиц.

 

Следующий код демонстрирует как представляются различные числа в IEEE

    public class Program
    {
        public static void Main(string[] args)
        {
            foreach (var v in Enumerable.Range(-10, 20))
                WriteBitsRepresentation(v);
        }

        private static void WriteBitsRepresentation(float v)
        {
            var c = new Complex
            {
                FloatValue = v
            };

            var allBits = Convert.ToString(c.UintValue, 2).PadLeft(32, '0');
            var signBits = GetBits(c.UintValue, 0, 1);
            var exponentBits = GetBits(c.UintValue, 1, 8);
            var mantissaBits = GetBits(c.UintValue, 9, 23);

            var mantissa = AfterPointBitsToDecimal(mantissaBits);
            var exponent = Convert.ToInt32(exponentBits, 2);
            var sign = signBits == "1";

            if (allBits == "00000000000000000000000000000000")
                Console.WriteLine("Zero");

            try
            {
                var checkedValue = (sign ? -1 : 1) * (1 + mantissa) * (decimal)Math.Pow(2, exponent - 127);

                if (Math.Abs(v - (float)checkedValue) > 0)
                {
                    Console.WriteLine("Error");
                    Console.WriteLine($"Expected {v} but {checkedValue}");
                    Console.WriteLine();

                    return;
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine();
            }

            Console.WriteLine(
                $"Bits: {allBits} = {(sign ? "-1 * " : "")}{1 + mantissa} * 2^({exponent} - 127) = {c.FloatValue}");

            Console.WriteLine($"Sign: {signBits} ({(sign ? "-" : "+")})");
            Console.WriteLine($"Exponent: {exponentBits} ({exponent})");
            Console.WriteLine($"Mantissa: 1.{mantissaBits} ({1 + mantissa})");
            Console.WriteLine();
        }

        public static decimal AfterPointBitsToDecimal(string bits)
        {
            const int maxPossibleDecimalBit = 63;
            var result = 0m;
            var index = 1;

            foreach (var bit in bits)
            {
                if (bit == '1')
                {
                    if (index > maxPossibleDecimalBit)
                        throw new IndexOutOfRangeException();

                    var divider = (ulong)1 << index;

                    result += 1m / divider;
                }

                index++;
            }

            return result;
        }

        public static string GetBits(uint b, int start, int length)
        {
            var startBit = 31 - start;
            var endBit = startBit - length;

            var sb = new StringBuilder();

            for (var i = startBit; i > endBit; i--)
            {
                var bit = GetBit(b, i);

                sb.Append(bit ? "1" : "0");
            }

            return sb.ToString();
        }

        private static bool GetBit(uint b, int bitNumber) => (b & (1 << bitNumber)) != 0;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct Complex
    {
        [FieldOffset(0)]
        public float FloatValue;

        [FieldOffset(0)]
        public uint UintValue;
    }