Основы C#

Как возвести число в степень в C#

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

В C# нет оператора степени: символ ^ здесь означает побитовое исключающее ИЛИ, поэтому 2 ^ 3 даёт 1, а не 8. Возвести число в степень можно через Math.Pow, обычный цикл, быстрое бинарное возведение или BigInteger.Pow для больших чисел. Ниже разобраны все способы, их точность, скорость и типичные ошибки.

Как возвести число в степень в C#
Содержание
  1. Почему ^ не работает как степень
  2. Метод Math.Pow
    1. Особые значения и граничные случаи
    2. Точность и целые результаты
  3. Возведение в степень циклом
  4. Бинарное возведение в степень
    1. Степень по модулю
  5. BigInteger.Pow для больших чисел
  6. Степень для decimal и других типов
  7. Какой способ выбрать
  8. Частые ошибки
  9. Частые вопросы

Почему ^ не работает как степень

Выходцы из математики и Excel привыкли писать 2^10, но в C# этот символ обозначает XOR. Компилятор не выдаст ошибку, программа просто посчитает другое число, и баг найти сложно. Для степени в языке нет специального оператора, вместо него используются методы библиотеки и собственные функции.

Console.WriteLine(2 ^ 3);          // 1, это XOR двоичных чисел 010 и 011
Console.WriteLine(Math.Pow(2, 3)); // 8

Для самых частых случаев вроде квадрата и куба методы не нужны: x * x и x * x * x короче, быстрее и не требуют преобразования типов. Метод имеет смысл, когда показатель степени задан переменной, дробный или отрицательный.

Метод Math.Pow

Основной инструмент находится в классе System.Math. Сигнатура: double Math.Pow(double x, double y), где x - основание, а y - показатель. Оба аргумента и результат имеют тип double, поэтому целые числа подставляются без явного приведения, а вот результат в int нужно приводить вручную.

double baseNumber = 2;
double exponent = 10;
double result = Math.Pow(baseNumber, exponent);
Console.WriteLine(result);           // 1024

int squared = (int)Math.Pow(7, 2);   // 49
Console.WriteLine(squared);

Console.WriteLine(Math.Pow(2, -2));  // 0.25
Console.WriteLine(Math.Pow(9, 0.5)); // 3, то же, что квадратный корень
Console.WriteLine(Math.Pow(27, 1.0 / 3)); // около 3, кубический корень

Метод умеет работать с отрицательными и дробными показателями, поэтому им же извлекают корни. Для квадратного корня удобнее Math.Sqrt, для кубического есть Math.Cbrt. Подробнее об остальных функциях класса рассказано в статье про математические функции в C#.

Особые значения и граничные случаи

Метод не бросает исключений, а возвращает специальные значения double. Это важно помнить, если степень вычисляется по данным пользователя: ошибка проявится не падением программы, а странным числом в отчёте.

ВызовРезультатПояснение
Math.Pow(0, 0)1по соглашению любое число в нулевой степени равно 1
Math.Pow(0, -1)Infinityделение на ноль в вещественной арифметике
Math.Pow(-8, 1.0 / 3)NaNдробная степень отрицательного числа не определена
Math.Pow(10, 400)Infinityпереполнение диапазона double
Math.Pow(double.NaN, 0)1нулевая степень даёт 1 даже для NaN

Проверять результат можно через double.IsNaN и double.IsInfinity. Если вы получаете NaN там, где ждали число, сначала посмотрите на знак основания и на дробность показателя.

Точность и целые результаты

Результат Math.Pow вычисляется в двоичной арифметике с плавающей точкой, поэтому для небольших целых степеней он обычно точен, а для больших значений начинаются погрешности. Тип double хранит целые числа без потерь только до 2 в 53-й степени, то есть примерно до 9 квадриллионов. Всё, что больше, будет округлено. Причины такого поведения разобраны в материале про неточность вещественной арифметики.

double big = Math.Pow(3, 40);
Console.WriteLine(big);                  // 1.2157665459056929E+19, значение уже приблизительное
Console.WriteLine((long)Math.Round(Math.Pow(10, 15))); // 1000000000000000

Если нужно целое число, не обрезайте результат приведением сразу: значение вроде 24.999999999 превратится в 24. Сначала округлите через Math.Round, затем приводите к long. А если результат может выйти за 2 в 53-й степени, лучше вовсе уйти от double и использовать целочисленные алгоритмы или BigInteger.

Возведение в степень циклом

Самый наглядный способ для целых чисел - перемножать основание нужное число раз. Он не использует вещественную арифметику, поэтому результат точен, пока не случится переполнение. Чтобы переполнение не осталось незамеченным, оберните умножение в checked, тогда вместо молчаливо искажённого числа вы получите OverflowException.

static long PowLoop(long value, int exponent)
{
    if (exponent < 0)
        throw new ArgumentOutOfRangeException(nameof(exponent), "Показатель должен быть неотрицательным");

    long result = 1;
    for (int i = 0; i < exponent; i++)
    {
        result = checked(result * value);
    }
    return result;
}

Console.WriteLine(PowLoop(2, 10)); // 1024
Console.WriteLine(PowLoop(5, 3));  // 125

Недостаток очевиден: число операций равно показателю. Для степени 10 это мелочь, для миллиарда - вечность. Здесь помогает приём, который сокращает количество умножений до логарифма.

Бинарное возведение в степень

Идея в том, что любую степень можно разложить по двоичным разрядам показателя. Например, x в 13-й равно x в 8-й, умноженному на x в 4-й и на x в первой, ведь 13 = 8 + 4 + 1. Алгоритм по очереди возводит основание в квадрат и перемножает те квадраты, которым соответствует единичный бит показателя. Вместо 13 умножений получается около шести.

static long BinaryPow(long value, long exponent)
{
    long result = 1;
    while (exponent > 0)
    {
        if ((exponent & 1) == 1)
            result = checked(result * value);

        exponent >>= 1;
        if (exponent > 0)
            value = checked(value * value);
    }
    return result;
}

Console.WriteLine(BinaryPow(2, 30));  // 1073741824
Console.WriteLine(BinaryPow(3, 13));  // 1594323

Проверка exponent > 0 перед последним возведением основания в квадрат нужна, чтобы не получить ложное переполнение на ненужном шаге. Без неё BinaryPow(3, 39) упадёт, хотя результат в long помещается.

Степень по модулю

В олимпиадных задачах и криптографии часто требуется остаток от деления степени на число, например 7 в 1000000-й по модулю 1000000007. Считать саму степень бессмысленно, она огромна. Достаточно брать остаток после каждого умножения. Все промежуточные значения остаются меньше модуля, поэтому произведение двух таких чисел помещается в long, пока модуль не превышает примерно 3 миллиарда.

static long ModPow(long value, long exponent, long modulus)
{
    long result = 1;
    value %= modulus;
    while (exponent > 0)
    {
        if ((exponent & 1) == 1)
            result = result * value % modulus;
        value = value * value % modulus;
        exponent >>= 1;
    }
    return result;
}

Console.WriteLine(ModPow(7, 1_000_000, 1_000_000_007));

BigInteger.Pow для больших чисел

Когда результат не влезает в long, подключайте пространство имён System.Numerics. Структура BigInteger хранит целое произвольной длины, а статический метод BigInteger.Pow(BigInteger, int) возводит его в степень без потери точности. Показатель здесь типа int. Для степени по модулю есть готовый BigInteger.ModPow, и писать собственную реализацию обычно не нужно.

using System.Numerics;

BigInteger big = BigInteger.Pow(2, 200);
Console.WriteLine(big);
// 1606938044258990275541962092341162602522202993782792835301376

BigInteger mod = BigInteger.ModPow(7, 1_000_000, 1_000_000_007);
Console.WriteLine(mod);

BigInteger huge = BigInteger.Pow(new BigInteger(12345), 20);
Console.WriteLine(huge.ToString().Length); // количество цифр результата

Цена - скорость и память: операции с BigInteger заметно медленнее аппаратных. Использовать его стоит там, где точность важнее, например в криптографии, факториалах или задачах на длинную арифметику.

Степень для decimal и других типов

Для decimal метода Math.Pow нет: перегрузки принимают только double. Если показатель целый и небольшой, пишите цикл или бинарный алгоритм на decimal, так вы сохраните десятичную точность, нужную в финансах. Например, сложный процент считается как сумма, умноженная на коэффициент в степени числа периодов. О выборе между типами подробно рассказано в статье decimal против double.

static decimal PowDecimal(decimal value, int exponent)
{
    decimal result = 1m;
    for (int i = 0; i < Math.Abs(exponent); i++)
        result *= value;
    return exponent >= 0 ? result : 1m / result;
}

decimal deposit = 100_000m;
decimal rate = 1.07m;
Console.WriteLine(deposit * PowDecimal(rate, 5)); // сумма через 5 лет при 7% годовых

Начиная с .NET 7 у типов с плавающей точкой появились статические методы обобщённой математики, например double.Pow(x, y) и float.Pow(x, y). Они работают так же, как Math.Pow, но позволяют писать универсальный код через интерфейс IPowerFunctions<T>. Для одиночных вычислений разницы нет, выбирайте привычный Math.Pow.

Какой способ выбрать

СпособТипыТочностьКогда применять
x * xлюбые числовыеполная до переполненияквадрат и куб
Math.Powdoubleоколо 15-16 значащих цифрдробные и отрицательные показатели, корни, инженерные расчёты
Циклint, long, decimalполная до переполнениянебольшие целые показатели
Бинарный алгоритмint, longполная до переполнениябольшие показатели, степень по модулю
BigInteger.PowBigIntegerточная при любой длинеочень большие целые результаты

Частые ошибки

Первая ошибка - всё тот же ^. Вторая - целочисленное деление в показателе: выражение Math.Pow(8, 1 / 3) даёт 1, потому что 1 / 3 для целых равно нулю, и получается 8 в нулевой степени. Нужно писать 1.0 / 3, на эту ловушку отдельно указано в материале о целочисленном делении.

Третья ошибка - приведение результата к int без проверки диапазона. При значении больше int.MaxValue в контексте без checked вы получите случайное число. Четвёртая - сравнение результата Math.Pow с целым через ==: для надёжности используйте допуск, например Math.Abs(a - b) < 1e-9.

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

Как в C# написать x в квадрате?

Самый простой вариант - x * x. Он быстрее вызова метода и сохраняет тип переменной. Запись Math.Pow(x, 2) тоже работает, но вернёт double, и для целых чисел придётся приводить тип.

Есть ли в C# оператор степени?

Нет. Символ ^ выполняет побитовое XOR, а не возведение в степень. Используйте Math.Pow или собственную функцию.

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

Вычислите Math.Round(Math.Pow(a, b)) и приведите к int, либо воспользуйтесь циклом на целых числах. Последний вариант надёжнее, потому что исключает вещественные погрешности, а с checked ещё и сообщит о переполнении.

Как возвести в отрицательную степень?

Передайте отрицательный показатель в Math.Pow: Math.Pow(2, -3) вернёт 0.125. В собственных целочисленных реализациях считайте степень по модулю показателя и берите обратное значение, как в примере для decimal.

Чем заменить Math.Pow для очень больших чисел?

Типом BigInteger и методом BigInteger.Pow. Он считает точно, но медленнее. Для остатка от деления на модуль используйте BigInteger.ModPow.

Почему результат Math.Pow иногда неточен?

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