Что такое decimal в C# и чем отличается от double?
Считаю сумму заказа, и при сложении цен типа 0.1 + 0.2 вылезает 0.30000000000000004. Использую double. Коллега сказал «для денег бери decimal». Чем decimal реально отличается от double и почему именно для денег? И что за буква m в конце числа?
2 ответа
Ты наткнулся на классику. Дело в том, как число хранится в памяти.
double — это двоичное число с плавающей точкой (IEEE 754). Компьютер хранит его в двоичной системе, а дроби вроде 0.1 в двоичке периодические — как 1/3 в десятичной. Поэтому 0.1 хранится приближённо, и при сложении накапливается ошибка:
double d = 0.1 + 0.2;
Console.WriteLine(d); // 0.30000000000000004
decimal хранит число в десятичной форме (128 бит) и точно представляет десятичные дроби. Для денег это критично — там каждая копейка должна биться.
decimal price = 0.1m + 0.2m;
Console.WriteLine(price); // 0.3
Что за m. Это суффикс литерала: он говорит компилятору «это decimal, а не double». Без него 0.1 — это double, и присвоение в decimal без суффикса даст ошибку компиляции. По аналогии: f — float, d — double, m — decimal (m от money, удобно запоминать).
decimal total = 19.99m * 3; // правильно
// decimal bad = 19.99; // ошибка: 19.99 это double
Когда что использовать:
- decimal — деньги, финансы, любые величины, где важна точная десятичная арифметика. Минус: медленнее и диапазон по величине меньше.
- double — научные/инженерные расчёты, графика, физика, где нужен огромный диапазон и небольшая погрешность не страшна. Он быстрее (его считает FPU процессора).
Правило: считаешь рубли и копейки — decimal. Считаешь траекторию или среднюю температуру — double. И никогда не сравнивай double через == напрямую, сравнивай с допуском (epsilon).
Подкреплю практикой: в большинстве ORM и БД денежные поля маппятся именно на decimal (SQL-тип decimal/numeric), так что и в C# держи их decimal по всей цепочке — иначе на стыке получишь скрытые конвертации и потерю точности.
Ещё совет по округлению: для денег используй Math.Round(value, 2, MidpointRounding.AwayFromZero), потому что банковское округление по умолчанию (ToEven) может удивить на значениях вроде 2.5.