Как double хранится в памяти в C#?

Ответ

Тип double в C# — это 64-битное число с плавающей запятой, соответствующее стандарту IEEE 754. В памяти оно занимает 8 байт (64 бита) и состоит из трех частей:

  1. Знак (Sign) — 1 бит.
    • 0 — положительное число.
    • 1 — отрицательное число.
  2. Экспонента (Exponent) — 11 бит.
    • Хранит степень двойки для нормализованного числа. Используется смещенное представление (bias = 1023). Это означает, что фактическая экспонента вычисляется как хранимое_значение - 1023.
  3. Мантисса (Mantissa/Фракция) — 52 бита.
    • Хранит дробную часть числа. Для нормализованных чисел подразумевается ведущая 1 (скрытый бит), поэтому фактическая точность — 53 бита.

Формула представления: (-1)^знак * (1 + мантисса) * 2^(экспонента - 1023)

Пример в коде:

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
struct DoubleToLong
{
    [FieldOffset(0)] public double DoubleValue;
    [FieldOffset(0)] public long LongValue;
}

public class Program
{
    public static void Main()
    {
        double num = -123.456;
        var converter = new DoubleToLong { DoubleValue = num };
        long bits = converter.LongValue;

        // Извлекаем части
        long sign = (bits >> 63) & 0x01;
        long exponent = (bits >> 52) & 0x7FF;
        long mantissa = bits & 0xFFFFFFFFFFFFF;

        Console.WriteLine($"Число: {num}");
        Console.WriteLine($"Знак: {sign}");
        Console.WriteLine($"Экспонента (смещенная): {exponent}");
        Console.WriteLine($"Мантисса (hex): 0x{mantissa:X}");
    }
}

Практические следствия:

  • Точность: ~15-17 десятичных знаков.
  • Ошибки округления: Из-за двоичного представления некоторые десятичные дроби (например, 0.1) не могут быть представлены точно. Это приводит к известной проблеме 0.1 + 0.2 != 0.3.
  • Специальные значения:
    • double.NaN (Not a Number) — результат неопределенных операций (например, 0.0 / 0.0).
    • double.PositiveInfinity и double.NegativeInfinity — результат переполнения (например, 1.0 / 0.0).

Для точных финансовых расчетов используйте decimal.

Ответ 18+ 🔞

Слушай, а вот этот твой double в C# — это же просто пиздец какой интересный зверь, если разобраться! Ну, смотри, по сути это 64-битная хуйня с плавающей запятой, стандарт IEEE 754, все дела. В памяти занимает 8 байт, то есть 64 бита, и внутри себя она разбита на три куска, как говно в унитазе после хорошей закуски.

Первое — знак. Один бит, проще некуда: нолик — число положительное, единичка — отрицательное. Всё, ебись оно конём.

Дальше — экспонента. Вот тут уже 11 бит, и это, блядь, не просто степень двойки, а так называемое смещённое представление. То есть, чтобы хранить отрицательные степени, они к реальной экспоненте прибавляют 1023 (это bias, смещение). Так что когда ты смотришь на биты, помни: реальная степень = хранимое_значение - 1023. Хитро, да? Ну а как ещё, блядь, отрицательные степени хранить?

И третий кусок — мантисса, она же дробная часть. 52 бита. Но самый прикол в том, что для нормальных чисел там подразумевается ещё одна ведущая единичка, которая не хранится! Это называется скрытый бит. Поэтому фактическая точность — 53 бита, вот тебе и раз, ебать-копать.

Формула, по которой всё это собирается обратно в число, выглядит так: (-1)^знак * (1 + мантисса) * 2^(экспонента - 1023)

Ну а теперь, чтобы не быть голословным, давай посмотрим на код. Вот как можно разобрать double на части, как гречку на вилке:

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
struct DoubleToLong
{
    [FieldOffset(0)] public double DoubleValue;
    [FieldOffset(0)] public long LongValue;
}

public class Program
{
    public static void Main()
    {
        double num = -123.456;
        var converter = new DoubleToLong { DoubleValue = num };
        long bits = converter.LongValue;

        // Выковыриваем части по одному
        long sign = (bits >> 63) & 0x01;
        long exponent = (bits >> 52) & 0x7FF;
        long mantissa = bits & 0xFFFFFFFFFFFFF;

        Console.WriteLine($"Число: {num}");
        Console.WriteLine($"Знак: {sign}");
        Console.WriteLine($"Экспонента (смещенная): {exponent}");
        Console.WriteLine($"Мантисса (hex): 0x{mantissa:X}");
    }
}

Запустишь — увидишь, из какой оперы эти -123.456.

А теперь про практику, где собака-то зарыта.

Точность у него примерно 15-17 десятичных знаков. Вроде много, но...

Ошибки округления — вот где пиздец! Потому что внутри-то всё в двоичной системе, а многие наши любимые десятичные дроби (например, 0.1) в двоичной — бесконечная периодическая дробь. Её точно не представишь. Поэтому известная всем хуйня: 0.1 + 0.2 != 0.3. Проверь, если не веришь. Вот и вся магия, ебать колотить.

Ещё есть специальные значения, как будто в цирке:

  • double.NaN (Not a Number) — это когда сделали какую-то дичь, например, 0.0 / 0.0. Результат — не число.
  • double.PositiveInfinity и double.NegativeInfinity — бесконечности. Получаются, если на ноль поделить, например, 1.0 / 0.0.

И главный вывод, который надо вытатуировать на жопе: для денег double не годится вообще ни хуя! Там эти ошибки округления тебе всю бухгалтерию порежут, как тупым ножом. Для точных расчётов, особенно финансовых, есть decimal — вот его и юзай, и будет тебе счастье.