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, который возвращает знак числа — иногда полезно перед округлением, чтобы самому выбирать, куда “отрубать”.
2
Задача
C# SELF, 6 уровень, 4 лекция
Недоступна
Округление до заданного количества знаков после запятой
Округление до заданного количества знаков после запятой
2
Задача
C# SELF, 6 уровень, 4 лекция
Недоступна
Применение различных методов округления
Применение различных методов округления
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ