Как вы делаете * целочисленное * возведение в степень в C #?

Встроенная функция Math.Pow() в .NET поднимает базу double до степени double и возвращает результат double.

Как лучше всего сделать то же самое с целыми числами?

Добавлено: кажется, что можно просто привести Math.Pow() result к (int), но всегда ли это будет давать правильное число и не будет ошибок округления?


person Roman Starkov    schedule 20.12.2008    source источник
comment
Как написано в другом месте, с 2010 года (.NET 4.0) существует BigInteger.Pow метод, который выполняет целочисленное возведение в степень (требуется сборочная ссылка на System.Numerics.dll).   -  person Jeppe Stig Nielsen    schedule 13.02.2014


Ответы (10)


Довольно быстрый вариант может быть примерно таким:

int IntPow(int x, uint pow)
{
    int ret = 1;
    while ( pow != 0 )
    {
        if ( (pow & 1) == 1 )
            ret *= x;
        x *= x;
        pow >>= 1;
    }
    return ret;
}

Обратите внимание, что это не позволяет использовать отрицательные мощности. Я оставлю это вам в качестве упражнения. :)

Добавлено: О да, чуть не забыл - также добавьте проверку переполнения / потери значимости, иначе вас могут ждать несколько неприятных сюрпризов в будущем.

person Vilx-    schedule 20.12.2008
comment
это отлично подходит для большого показателя - person orip; 20.12.2008
comment
Зачем нужна явная проверка переполнения? Разве встроенная проверка переполнения C # не применима нормально? (Предполагая, что вы прошли / проверили) - person Jay Bazuzi; 21.12.2008
comment
Алгоритмическое название этого - возведение в степень путем повторного возведения в квадрат. По сути, мы многократно удваиваем x, и если у pow есть 1 бит в этой позиции, мы умножаем / накапливаем его в возвращаемое значение. - person ioquatix; 23.11.2012
comment
Использование BigInteger в .NET 4 дает действительно большие целочисленные возможности - person bugmagnet; 09.08.2013
comment
@boost BigInteger имеет встроенное питание - person harold; 04.10.2013
comment
Это потрясающе! Мне всегда было интересно, как лучше всего вычислять мощности, поскольку наивный алгоритм для этого имеет паршивую временную сложность. Этот гарантированно вернется в течение 32 итераций (а средний случай будет намного ниже). И если вы повторно реализуете его на долгое время, он только удвоит максимальное количество итераций. - person Miles B.; 30.01.2019
comment
@MilesB. - Имейте в виду, что с Int32 вы не получите ничего выше 32-й степени, так что наивный алгоритм в любом случае подойдет. :) На самом деле то же самое и с Int64 - 64 итераций на самом деле не так уж и много, тем более что современные процессоры будут выполнять все это в кеш-памяти процессора или даже в регистрах. Выигрыш в скорости от этого необычного алгоритма будет заметен только в очень экзотических условиях. - person Vilx-; 30.01.2019
comment
@Vilx - Достаточно верно. Я просто немного параноик, когда дело касается эффективности ... - person Miles B.; 30.01.2019
comment
@MilesB. Справедливо. :) Раньше я тоже был таким. Но затем я начал работать над некоторым кодом, который проделал потрясающую работу в довольно обычном коде Javascript (на стороне браузера), и я понял, что современные компьютеры уже довольно эффективны. Если вы на самом деле не сделаете что-то излишне сложное, это почти всегда будет работать нормально без тщательной оптимизации. И несколько мест, где этого не происходит - вы можете позаботиться о них в индивидуальном порядке. - person Vilx-; 30.01.2019
comment
@MilesB. В наши дни мой приоритет - сделать мой код максимально читаемым и понятным. Никаких ошеломляющих умных оптимизаций; никаких магических конструкций, которые неявно выполняют сложные вещи без видимого кода; и т. д. Что удивительно, проблемы с производительностью случаются редко. - person Vilx-; 31.01.2019

LINQ кто-нибудь?

public static int Pow(this int bas, int exp)
{
    return Enumerable
          .Repeat(bas, exp)
          .Aggregate(1, (a, b) => a * b);
}

использование как расширение:

var threeToThePowerOfNine = 3.Pow(9);
person 3dGrabber    schedule 09.08.2012
comment
Это самый веселый ответ, который я видел сегодня - поздравляю с тем, что он работает так, как ожидалось: D - person ioquatix; 23.11.2012
comment
@ioquatix - вот как вы бы сделали это на языке функционального программирования с невозмутимым лицом. - person Martin Capodici; 12.08.2015
comment
@MartinCapodici Я всегда улыбаюсь, когда пишу код. Либо так, либо я иногда гримасничаю, когда читаю чужой код. Обычно у меня нет серьезного лица :) - person ioquatix; 13.08.2015

Используя математику из ссылки в блоге Джона Кука,

    public static long IntPower(int x, short power)
    {
        if (power == 0) return 1;
        if (power == 1) return x;
        // ----------------------
        int n = 15;
        while ((power <<= 1) >= 0) n--;

        long tmp = x;
        while (--n > 0)
            tmp = tmp * tmp * 
                 (((power <<= 1) < 0)? x : 1);
        return tmp;
    }           

чтобы ответить на возражение, что код не будет работать, если вы измените тип питания, ну ... не говоря уже о том, что любой, кто меняет код, он не понимает, а затем использует его без тестирования ...
но Чтобы решить эту проблему, эта версия защищает глупых от этой ошибки ... (но не от множества других, которые они могут совершить) ПРИМЕЧАНИЕ: не тестировалось.

    public static long IntPower(int x, short power)
    {
        if (power == 0) return 1;
        if (power == 1) return x;
        // ----------------------
        int n = 
            power.GetType() == typeof(short)? 15:
            power.GetType() == typeof(int)? 31:
            power.GetType() == typeof(long)? 63: 0;  

        long tmp = x;
        while (--n > 0)
            tmp = tmp * tmp * 
                 (((power <<= 1) < 0)? x : 1);
        return tmp;
    }

Также попробуйте этот рекурсивный эквивалент (медленнее, конечно):

    public static long IntPower(long x, int power)
    {
        return (power == 0) ? x :
            ((power & 0x1) == 0 ? x : 1) *
                IntPower(x, power >> 1);
    }
person Charles Bretana    schedule 21.12.2008
comment
Убедитесь, что вы используете это, чтобы не изменять его вообще. Я думал, что смогу использовать short, чтобы избежать чего-либо, но алгоритм не работает, если это не так. Я предпочитаю более простой, но менее производительный метод Vilx - person Eric Stassen; 18.11.2011
comment
обсидиан, вы можете использовать int, если измените 15 в алгоритме на 31 - person Charles Bretana; 08.01.2012
comment
Я провел краткий тест и, как я подозревал, метод Вилкса более эффективен, если вам нужны степени int-length (примерно в 6 раз быстрее). Может, кто-нибудь еще сможет проверить этот результат? - person ioquatix; 23.11.2012
comment
ГОЛОВА ВВЕРХ - Как сказал Обсидиан, это не сработает, если вы ИЗМЕНИТЕ ТИП СИЛЫ. Извините за все заглавные буквы, но кажется, что это действительно действительно нужно назвать. - person pfrank; 02.04.2016
comment
ДА, ДА ... (вам просто нужно изменить значение 15 на длину типа, используемого в экспоненте.) - person Charles Bretana; 03.04.2016

Как насчет:

public static long IntPow(long a, long b)
{
  long result = 1;
  for (long i = 0; i < b; i++)
    result *= a;
  return result;
}
person mini-me    schedule 11.04.2012
comment
Просто, хотя и требуется проверка на отрицательный b. - person mklement0; 06.02.2020
comment
Обратите внимание, что временная сложность этого кода составляет O (n), где n - степень, тогда как в верхнем ответе это O (log (n)), что намного лучше для больших степеней. - person Bip901; 10.12.2020

Очень интересно .. в .net 5.0 SimplePower () теперь в 350 раз быстрее. И я бы сказал лучше всего по переносимости / производительности / удобочитаемости ...

    public static int SimplePower(int x, int pow)
    {
        return (int)Math.Pow(x, pow);
    }

Вот еще один, который я построил в прошлом, он был быстрым ...

    public static int PowerWithSwitch(int x, int pow)
    {
        switch ((uint)pow)
        {
            case 0: return 1;
            case 1: return x;
            case 2: return x * x;
            case 3: return x * x * x;
            case 4: { int t2 = x * x; return t2 * t2; }
            case 5: { int t2 = x * x; return t2 * t2 * x; }
            case 6: { int t3 = x * x * x; return t3 * t3; }
            case 7: { int t3 = x * x * x; return t3 * t3 * x; }
            case 8: { int t3 = x * x * x; return t3 * t3 * x * x; }
            case 9: { int t3 = x * x * x; return t3 * t3 * t3; }
            case 10: { int t3 = x * x * x; return t3 * t3 * t3 * x; }
            case 11: { int t3 = x * x * x; return t3 * t3 * t3 * x * x; }
            case 12: { int t3 = x * x * x; return t3 * t3 * t3 * t3; }
            case 13: { int t3 = x * x * x; return t3 * t3 * t3 * t3 * x; }
            case 14: { int t4 = x * x * x * x; return t4 * t4 * t4 * x * x; }
            case 15: { int t4 = x * x * x * x; return t4 * t4 * t4 * x * x * x; }
            case 16: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4; }
            case 17: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * x; }
            case 18: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * x * x; }
            case 19: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * x * x * x; }
            case 20: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4; }
            case 21: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * x; }
            case 22: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * x * x; }
            case 23: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * x * x * x; }
            case 24: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4; }
            case 25: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * x; }
            case 26: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * x * x; }
            case 27: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * x * x * x; }
            case 28: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * t4; }
            case 29: { int t4 = x * x * x * x; return t4 * t4 * t4 * t4 * t4 * t4 * t4 * x; }
            default:
                if (x == 0)
                    return 0;
                else if (x == 1)
                    return 1;
                else
                    return (x % 1 == 0) ? int.MaxValue : int.MinValue;
        }
        return 0;
    }

Тестирование производительности (.Net 5)


  • MathPow (Sunsetquest): 11 мс (.net 4 = 3693 мс) ‹- в 350 раз быстрее !!!

  • PowerWithSwitch (Sunsetquest): 145 мс (.net 4 = 298 мс)

  • Vilx: 148 мс (.net 4 = 320 мс)

  • Эван Моран - рекурсивное деление: 249 мс (.net 4 = 644 мс)

  • mini-me: 288 мс (.net 4 = 194 мс)

  • Чарльз Бретана (он же Cook's): 536 мс (.net 4 = 950 мс)

  • Версия LINQ: 4416 мс (.net 4 = 3693 мс)

(примечания к тестированию: AMD Threadripper Gen1, .Net 4 и 5, сборка выпуска, отладчик не подключен, базы: 0–100 КБ, срок действия: 0–10)

Примечание. В приведенных выше тестах проверка точности была незначительной.

person SunsetQuest    schedule 13.02.2014
comment
производительность mini-me будет сохраняться только при меньших полномочиях. Но я определенно использую ваш код для решения проблемы 43: projecteuler.net/problem=43 - person turiyag; 28.12.2014
comment
Показатели от 0 до 30 для баз от 0 до 1M и Vilx- в 2 раза быстрее; для показателей от 0 до 100 это в 4 раза быстрее. - person NetMage; 08.05.2019

Использовать двойную версию, проверить переполнение (over max int или max long) и привести к int или long?

person bh213    schedule 20.12.2008
comment
Как узнать, что это не приведет к неправильным результатам из-за ошибок округления? - person Roman Starkov; 20.12.2008
comment
Добавьте 0,5 перед преобразованием в int, чтобы позаботиться об округлении, если точность double больше, чем точность int или long. - person Mark Ransom; 21.12.2008
comment
Двойники могут представлять все целые числа до 2 ^ 53, так что похоже, что это всегда будет работать. - person Roman Starkov; 21.12.2008
comment
Если вы не используете 64-битные целые числа. - person dan04; 01.05.2010

Мое любимое решение этой проблемы - классическое рекурсивное решение «разделяй и властвуй». Это на самом деле быстрее, чем умножение в n раз, поскольку каждый раз уменьшает количество умножений вдвое.

public static int Power(int x, int n)
{
  // Basis
  if (n == 0)
    return 1;
  else if (n == 1)
    return x;

  // Induction
  else if (n % 2 == 1)
    return x * Power(x*x, n/2);
  return Power(x*x, n/2);
}

Примечание: это не проверяет переполнение или отрицательное n.

person Evan Moran    schedule 01.07.2011
comment
Это тот же алгоритм, что и Vilx-, за исключением того, что он использует гораздо больше места (рекурсивный вызов не является хвостовым вызовом). - person Ben Voigt; 26.06.2012

Я передаю результат в int, например:

double exp = 3.0;
int result = (int)Math.Pow(2.0, exp);

В этом случае ошибок округления нет, поскольку основание и экспонента являются целыми числами. Результат тоже будет целым.

person Claudio    schedule 08.05.2015
comment
Попробуйте Math.Pow (7, 19). Есть ошибки, связанные с поплавком. - person N-ate; 06.08.2015
comment
@ N-ate 7^19 в любом случае слишком велик для Int32, поэтому вы не будете выполнять приведение к int, если знаете, что ваши числа такие большие. - person Bip901; 10.12.2020

Для короткого и быстрого однострочника.

int pow(int i, int exp) => (exp == 0) ? 1 : i * pow(i, exp-1);

Нет никаких проверок отрицательной экспоненты или переполнения.

person Ralph    schedule 02.09.2017

Другой способ:

int Pow(int value, int pow) {
    var result = value;
    while (pow-- > 1)
        result *= value;
    return pow == 0 ? result : pow == -1 ? 1 : throw new ArgumentOutOfRangeException(nameof(pow));
}
person High    schedule 09.06.2020