Рассмотрим представление для типа 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;
}