JavaRush /Курси /C# SELF /Округлення дійсних типів

Округлення дійсних типів

C# SELF
Рівень 6 , Лекція 4
Відкрита

1. Вступ

У реальному житті рідко буває, щоб гроші виплачували з точністю до сто трильйонних євро, а довжину кімнати вимірювали до сотих міліметра. У програмуванні так само: результати роботи з double чи float часто бувають надто дробовими.

Ось класична проблема:

double result = 10.0 / 3.0;
Console.WriteLine(result); // 3.3333333333333335
Ділення з плаваючою комою — результат із «хвостом»

А якщо вам потрібно вивести це число, наприклад, лише з двома знаками після коми (щоб не «лякати» користувача)? Для цього й потрібне округлення.

2. Основні методи округлення в C#

C# пропонує кілька способів округлити дійсне число. І тут починається цікаве: округлення буває різним! Розгляньмо основні варіанти й з’ясуймо, чим вони відрізняються.

1) Функція Math.Round — класичне округлення

Найпопулярніший спосіб — метод Math.Round. Він округлює число до найближчого цілого (або до потрібної кількості знаків після коми).


Math.Round(число[, кількість_знаків[, MidpointRounding]])
Синтаксис Math.Round
  • число — що округлюємо.
  • кількість_знаків — скільки залишити знаків після коми (за замовчуванням 0, тобто до цілого).
  • MidpointRounding — метод округлення половинок (про нього поговоримо далі).

Приклади:

double x = 2.71828;

Console.WriteLine(Math.Round(x));         // 3
Console.WriteLine(Math.Round(x, 2));      // 2.72
Console.WriteLine(Math.Round(x, 3));      // 2.718

Візуалізація:

Вхідне значення Math.Round(x) Math.Round(x, 2)
2.3 2 2.3
2.5 2 2.5
2.7 3 2.7
2.71828 3 2.72

Тип результату:
Усі функції в цій лекції повертають тип double, тому, якщо ви хочете присвоїти результат округлення, можете отримати помилку:

double result = 10.0 / 5.0;     // Результат 2.0
int x = Math.Round(x);          // Помилка! Не можна просто так присвоїти значення типу double змінній типу int

Якщо потрібно присвоїти значення типу double змінній типу int, слід виконати явне перетворення типу:

double result = 10.0 / 5.0;     // Результат 2.0
int x = (int) Math.Round(x);    // Усе чудово працює!

2) Округлення в більшу або меншу сторону

C# підтримує не лише «класичне» округлення, а й примусове округлення вгору або вниз.

Округлення до меншого (Math.Floor)

Метод Math.Floor просто відкидає дробову частину (у бік мінусної нескінченності). Тобто завжди округлює вниз!

Console.WriteLine(Math.Floor(2.99));   // 2
Console.WriteLine(Math.Floor(-2.99));  // -3

Округлення до більшого (Math.Ceiling)

Метод Math.Ceiling — протилежність Floor: він завжди округлює вгору (у бік додатної нескінченності).

Console.WriteLine(Math.Ceiling(2.01));   // 3
Console.WriteLine(Math.Ceiling(-2.01));  // -2

Порівняльна схема:

Функція 2.3 -2.3
Math.Round 2 -2
Math.Floor 2 -3
Math.Ceiling 3 -2

3) Truncate: просто обрізати дробову частину

Метод Math.Truncate — «спокійний хірург». Він просто видаляє дробову частину, не переймаючись математичною коректністю з точки зору округлення. Якщо x додатнє — результат як у Floor, якщо відʼємне — як у Ceiling.

Console.WriteLine(Math.Truncate(2.99));   // 2
Console.WriteLine(Math.Truncate(-2.99));  // -2

3. Як працює Math.Round для «половинок»? (MidpointRounding)

Уявіть: у вас є 2.5. Треба округляти — але куди? Наче «порівну» між 2 і 3. У математиці є два популярні підходи:

  • Округлення в більшу сторону («до найближчого більшого»)
  • Округлення до найближчого парного (bankerʼs rounding)

C# за замовчуванням використовує другий підхід — округлення до найближчого парного («bankerʼs rounding», або «округлення в бік парного»).

Console.WriteLine(Math.Round(2.5)); // 2
Console.WriteLine(Math.Round(3.5)); // 4

Чому? Бо з двох однаково віддалених цілих парне «виграє». Це зроблено, щоб за великої кількості округлень не виникала систематична похибка в бік збільшення чи зменшення. Це дуже важливо, наприклад, під час підрахунку великих сум грошей у банку (звідси й назва).

Якщо ви хочете завжди округлювати вгору, можна явно вказати потрібний режим:

// Округлювати завжди «від нуля»
Console.WriteLine(Math.Round(2.5, 0, MidpointRounding.AwayFromZero)); // 3
Console.WriteLine(Math.Round(-2.5, 0, MidpointRounding.AwayFromZero)); // -3

Таблиця режимів MidpointRounding:

Вхідне значення Round (за замовчуванням) AwayFromZero ToZero / ToEven
2.5 2 3 2
3.5 4 4 4
-2.5 -2 -3 -2

4. Округлення до потрібної кількості знаків: не забудьте про «вісімки» й «трійки»!

Іноді потрібно округлити не до цілого, а, скажімо, до двох знаків після коми (наприклад, для виведення грошей чи відсотків).

double price = 149.9999;
double roundedPrice = Math.Round(price, 2);
Console.WriteLine(roundedPrice); // 150

Що, якщо ми хочемо просто «відкинути» зайві знаки, але не округлювати? Наприклад, з 123.4567 зробити 123.45.

Це можна зробити за допомогою невеликого прийому:

double num = 123.4567;
double result = Math.Floor(num * 100) / 100;
Console.WriteLine(result); // 123.45

Ми вручну «зсуваємо» десяткову кому, відтинаємо дробову частину й повертаємо кому назад.

5. Форматування результату виведення vs округлення

ВАЖЛИВО!

Форматування виведення ({x:F2}) — це не завжди «справжнє» округлення, а лише «маска» того, як число виглядає на екрані. У памʼяті воно залишиться тим самим довгим «хвостатим» double. Якщо вам потрібно саме округлити значення й зберегти його — використовуйте Math.Round.

Ось приклад, де різниця може бути критичною:

double value = 2.555;
Console.WriteLine($"{value:F2}"); // 2.56
Console.WriteLine(Math.Round(value, 2)); // 2.56

double stored = Math.Round(value, 2);
Console.WriteLine(stored); // 2.56

// Але якщо просто вивести без округлення, але з форматом...
Console.WriteLine($"{value:F2}"); // 2.56, але в памʼяті — 2.555

6. Типові помилки і нюанси

  • Не використовуйте округлення для точних фінансових розрахунків із double! Для грошей у .NET є спеціальний тип decimal — цей тип уникає похибок, властивих типам із плаваючою комою (детальніше — в одній із наступних лекцій).
  • Сумнозвісні «завислі вісімки»: через двійкове подання не всі числа округлюються «логічно» «на око». Наприклад, 0.1 + 0.2 може бути зовсім не 0.3.
  • Не плутайте Math.Floor і Math.Round! Перший — завжди вниз, другий — як того вимагають правила округлення.
  • Некероване накопичення похибки: якщо ви часто округлюєте результат обчислень на проміжних етапах (наприклад, всередині циклу), підсумкове значення може несподівано «поплисти». Зазвичай краще округлювати лише кінцевий результат, а не всі проміжні кроки.

7. Фінальна таблиця: який метод що дає?

Вхідне значення Math.Round(x) Math.Floor(x) Math.Ceiling(x) Math.Truncate(x)
3.2 3 3 4 3
3.5 4 3 4 3
-3.2 -3 -4 -3 -3
-3.5 -4 -4 -3 -3

8. Кумедні факти

  • Якось NASA втратила космічний апарат через неузгодженість округлення координат і різницю між метричними та імперськими одиницями. Мораль: округлюйте свідомо!
  • Банки давно перейшли на «bankerʼs rounding», щоб не втрачати копійки — раніше по-старому всі округлювали вгору, і клієнти втрачали частки цента на кожному переказі.
  • У C# є навіть метод Math.Sign, який повертає знак числа — іноді корисно перед округленням, щоб самостійно обирати, куди «відрубати».
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ