Основы C#

Математические функции в C# (Си Шарп)

Обновлено: · 6 мин чтения

Почти все математические функции C# собраны в статическом классе System.Math: округление, модуль, степень, корни, логарифмы, тригонометрия и сравнение чисел. Ниже разобрано, как они работают, какие у них подводные камни и какие новые методы появились в .NET 6-8. Каждый пример можно скопировать в консольный проект и запустить.

Математические функции в C# (Си Шарп)
Содержание
  1. Класс Math и его константы
  2. Основные функции: модуль, знак, минимум и максимум
  3. Округление чисел
    1. Целочисленное деление и остаток
  4. Степени, корни и экспонента
  5. Логарифмы
  6. Тригонометрия
  7. Проверка результата на корректность
  8. Сводная таблица методов
  9. Функции для decimal и обобщённая математика
  10. Типичные ошибки
  11. Частые вопросы

Класс Math и его константы

Класс Math не требует создания объекта и подключения дополнительных пространств имён, достаточно стандартной директивы using System;, которая в новых проектах добавляется автоматически. Рядом существует класс MathF с теми же методами для типа float. Для вычислений с ним сохраняются те же правила, но результаты возвращаются с меньшей точностью. Кроме методов, в классе есть две константы: Math.PI (число пи) и Math.E (основание натурального логарифма). В .NET 5 и новее добавлена ещё Math.Tau, равная 2 пи.

Console.WriteLine(Math.PI);  // 3.141592653589793
Console.WriteLine(Math.E);   // 2.718281828459045
Console.WriteLine(Math.Tau); // 6.283185307179586

double r = 5;
double circleArea = Math.PI * r * r;
Console.WriteLine($"Площадь круга: {circleArea:F2}"); // 78,54

Большинство методов принимает и возвращает double. Для целых и других числовых типов существуют перегрузки не у всех функций, поэтому помните о неявных преобразованиях и о том, что результат вещественной функции придётся приводить к целому вручную.

Основные функции: модуль, знак, минимум и максимум

Эти методы встречаются в каждой второй программе. Math.Abs возвращает модуль числа, Math.Sign - знак (-1, 0 или 1), а Math.Min и Math.Max выбирают меньшее и большее из двух значений. Для ограничения числа диапазоном лучше использовать Math.Clamp, он заменяет пару вложенных вызовов Min и Max.

Console.WriteLine(Math.Abs(-5));        // 5
Console.WriteLine(Math.Sign(-20));      // -1
Console.WriteLine(Math.Max(5, 10));     // 10
Console.WriteLine(Math.Min(5, 10));     // 5
Console.WriteLine(Math.Clamp(150, 0, 100)); // 100
Console.WriteLine(Math.Clamp(-3, 0, 100));  // 0

Есть один неочевидный случай: Math.Abs(int.MinValue) бросает OverflowException, потому что положительного аналога у минимального значения int нет. Если данные приходят извне, приведите число к long перед вычислением или проверьте значение заранее.

Округление чисел

Округление - главный источник путаницы. Math.Round по умолчанию применяет банковское округление: значение ровно посередине между двумя целыми округляется к ближайшему чётному. Поэтому 2.5 превращается в 2, а 3.5 - в 4. Чтобы получить школьное правило "от нуля", передайте режим MidpointRounding.AwayFromZero.

Console.WriteLine(Math.Round(2.5));  // 2
Console.WriteLine(Math.Round(3.5));  // 4
Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero)); // 3

Console.WriteLine(Math.Round(3.14159, 2)); // 3.14
Console.WriteLine(Math.Round(0.125, 2));   // 0.12
Console.WriteLine(Math.Round(0.125, 2, MidpointRounding.AwayFromZero)); // 0.13

Остальные функции округляют всегда в одну сторону. Floor идёт вниз, Ceiling вверх, а Truncate отбрасывает дробную часть. Разница заметна на отрицательных числах.

ЗначениеFloorCeilingTruncateRound
3.73434
3.23433
-3.7-4-3-3-4
-3.2-4-3-3-3
2.52322

Чтобы вывести число с двумя знаками после запятой, не всегда нужно округлять значение: достаточно формата ToString("F2"). Эти два сценария мы развели в отдельной статье про отображение чисел с двумя знаками. Округление до сотен и тысяч выполняется делением, округлением и умножением: Math.Round(x / 100) * 100.

Целочисленное деление и остаток

Для целых чисел деление и остаток часто нужны вместе. Метод Math.DivRem возвращает оба значения за один вызов, начиная с .NET 6 он отдаёт кортеж. Операция % в C# сохраняет знак делимого, поэтому -7 % 3 равно -1. Если нужен неотрицательный остаток, например для индекса по кругу, прибавьте делитель и снова возьмите остаток.

var (quotient, remainder) = Math.DivRem(17, 5);
Console.WriteLine($"{quotient} и {remainder}"); // 3 и 2

int index = -1;
int size = 5;
int wrapped = ((index % size) + size) % size;
Console.WriteLine(wrapped); // 4

Степени, корни и экспонента

Метод Math.Pow(x, y) возводит число в степень, и вся его специфика описана в статье про возведение в степень. Math.Sqrt извлекает квадратный корень, Math.Cbrt - кубический. Для отрицательного аргумента Sqrt возвращает NaN, а не бросает исключение, поэтому проверяйте данные заранее. Math.Exp(x) вычисляет число e в степени x.

Console.WriteLine(Math.Sqrt(9));      // 3
Console.WriteLine(Math.Sqrt(-1));     // NaN
Console.WriteLine(Math.Cbrt(27));     // 3
Console.WriteLine(Math.Exp(1));       // 2.718281828459045
Console.WriteLine(Math.Pow(2, 10));   // 1024

// длина вектора (теорема Пифагора)
double a = 3, c = 4;
Console.WriteLine(Math.Sqrt(a * a + c * c)); // 5

Логарифмы

В классе есть натуральный логарифм Math.Log(x), десятичный Math.Log10(x) и двоичный Math.Log2(x). Для произвольного основания используется перегрузка Math.Log(x, base). Логарифм нуля равен минус бесконечности, а логарифм отрицательного числа - NaN. Типичные применения: определение количества цифр числа, количества бит и расчёт глубины дерева.

Console.WriteLine(Math.Log(Math.E));    // 1
Console.WriteLine(Math.Log10(1000));    // 3
Console.WriteLine(Math.Log2(1024));     // 10
Console.WriteLine(Math.Log(81, 3));     // 4

int number = 12345;
int digits = (int)Math.Floor(Math.Log10(number)) + 1;
Console.WriteLine(digits); // 5

Приём с подсчётом цифр работает для положительных чисел. Для нуля и отрицательных значений нужна отдельная обработка, иначе получите бесконечность или NaN.

Тригонометрия

Функции Sin, Cos, Tan принимают угол в радианах, а не в градусах. Это самая частая ошибка: Math.Sin(90) не равно единице. Переводите градусы формулой радианы = градусы * Math.PI / 180. Обратные функции Asin, Acos, Atan возвращают радианы. Для вычисления угла по координатам существует Math.Atan2(y, x), он правильно определяет четверть и не делит на ноль.

static double ToRadians(double degrees) => degrees * Math.PI / 180;
static double ToDegrees(double radians) => radians * 180 / Math.PI;

double angle = ToRadians(30);
Console.WriteLine(Math.Sin(angle));            // 0.49999999999999994
Console.WriteLine(Math.Round(Math.Sin(angle), 6)); // 0.5
Console.WriteLine(ToDegrees(Math.Atan2(1, 1)));   // 45
Console.WriteLine(Math.Cos(Math.PI / 2));      // 6.123233995736766E-17, а не 0

Обратите внимание на результаты: из-за двоичного представления чисел значения вроде 0.49999999999999994 и 6.1E-17 отличаются от идеальных. Сравнивать такие числа через == нельзя, используйте допуск или округление. Причины подробно объяснены в статье почему вещественные числа неточны. Заодно в .NET 7 появились методы double.DegreesToRadians и double.RadiansToDegrees, которые можно использовать вместо собственных функций.

Гиперболические функции Sinh, Cosh, Tanh и обратные им Asinh, Acosh, Atanh нужны реже, в основном в научных расчётах и активационных функциях нейронных сетей.

Проверка результата на корректность

Математические методы редко бросают исключения. Вместо этого они возвращают NaN или бесконечность, и неверное значение молча попадает в дальнейшие расчёты. Любая операция с NaN даёт NaN, а сравнение NaN с чем угодно, даже с самим собой, возвращает false. Для проверки используйте специальные методы типа double.

double x = Math.Sqrt(-4);
Console.WriteLine(double.IsNaN(x));          // True
Console.WriteLine(x == double.NaN);          // False, так сравнивать нельзя
Console.WriteLine(double.IsInfinity(Math.Log(0))); // True
Console.WriteLine(double.IsFinite(5.0));     // True

Сводная таблица методов

МетодЧто делаетПример
Absмодуль числаMath.Abs(-7) = 7
Signзнак числаMath.Sign(-3.5) = -1
Min, Maxменьшее, большееMath.Max(2, 9) = 9
Clampограничение диапазономMath.Clamp(12, 0, 10) = 10
RoundокруглениеMath.Round(2.567, 1) = 2.6
Floor, Ceilingвниз, вверхMath.Ceiling(4.1) = 5
Truncateотбрасывание дробной частиMath.Truncate(-4.9) = -4
Sqrt, Cbrtкорень квадратный, кубическийMath.Sqrt(16) = 4
PowстепеньMath.Pow(2, 5) = 32
Expe в степени xMath.Exp(0) = 1
Log, Log10, Log2логарифмыMath.Log10(100) = 2
Sin, Cos, Tanтригонометрия в радианахMath.Cos(0) = 1
Atan2угол по координатамMath.Atan2(1, 1) = 0.785
DivRemчастное и остатокMath.DivRem(17, 5) = (3, 2)

Функции для decimal и обобщённая математика

Для финансовых расчётов на типе decimal доступны не все методы: у него есть перегрузки Abs, Round, Floor, Ceiling, Truncate, Min, Max, Sign, но нет тригонометрии, логарифмов и степени. Когда нужны такие операции, придётся переходить на double и учитывать потерю десятичной точности. Критерии выбора типа разбираются в материале decimal против double.

Начиная с .NET 7 появилась обобщённая математика: интерфейсы INumber<T>, IFloatingPoint<T> и другие позволяют написать один метод для любого числового типа. Многие функции доступны и как статические методы самих типов: int.Abs, double.Sqrt, double.Clamp, int.Log2.

using System.Numerics;

static T Average<T>(IEnumerable<T> items) where T : INumber<T>
{
    T sum = T.Zero;
    int count = 0;
    foreach (var item in items)
    {
        sum += item;
        count++;
    }
    return sum / T.CreateChecked(count);
}

Console.WriteLine(Average(new[] { 1, 2, 3, 4 }));        // 2
Console.WriteLine(Average(new[] { 1.5, 2.5, 3.5 }));     // 2,5

Заметьте, что для целых чисел результат деления тоже целый, как объяснено в статье о целочисленном делении. Для среднего значения целых чисел вычисляйте сумму в double.

Типичные ошибки

  • Подача градусов в тригонометрические функции вместо радиан.
  • Сравнение вещественных результатов через == без допуска.
  • Ожидание школьного округления от Math.Round: без параметра он округляет до чётного.
  • Деление целых чисел там, где нужен дробный результат: 1 / 2 равно нулю.
  • Приведение результата к int без проверки диапазона и без предварительного округления.
  • Игнорирование NaN и бесконечности после Sqrt, Log и деления на ноль.

Частые вопросы

Какой класс отвечает за математику в C#?

Статический класс System.Math. Для типа float есть MathF, для больших целых - System.Numerics.BigInteger, а для обобщённых вычислений интерфейсы INumber<T> и родственные.

Почему Math.Round(2.5) возвращает 2?

По умолчанию действует банковское округление к чётному. Чтобы получить 3, передайте MidpointRounding.AwayFromZero.

Как вычислить синус угла в градусах?

Переведите градусы в радианы: Math.Sin(градусы * Math.PI / 180). Сами методы Sin, Cos и Tan принимают только радианы.

Как извлечь корень n-й степени?

Используйте Math.Pow(x, 1.0 / n) для положительных чисел. Для квадратного корня применяйте Math.Sqrt, для кубического Math.Cbrt. Запись 1 / n с целыми числами даст ноль, поэтому пишите 1.0 / n.

Как получить случайное число и есть ли оно в Math?

В классе Math генератора нет. Случайные числа создаются классом Random, подробности в статье про случайные числа в C#.

Что вернёт Math.Sqrt от отрицательного числа?

Значение double.NaN. Исключение не бросается, поэтому результат нужно проверять через double.IsNaN.