В Дзене Python есть принцип, согласно которому "должен существовать один и желательно только один очевидный способ сделать это". Однако в Python есть как минимум три способа возведения числа в степень:

  • оператор **

  • встроенная функция pow()

  • функция math.pow()

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

Оператор **

Оператор ** — это классический оператор возведения в степень. Это в принципе первое, что обычно приходит в голову любому программисту на Python, когда требуется возвести число в степень.

Приведенный ниже код:

print(2 ** 10)        # 2 в 10-й степени
print(10 ** 2)        # 10 во 2-й степени

выводит:

1024
100

Важно отметить, что оператор ** работает со всеми числовыми типами в Python: int, float, Fraction, Decimal, complex.

Приведенный ниже код:

from fractions import Fraction
from decimal import Decimal

nums = [100, 0.3, Fraction(2, 5), Decimal('0.3'), complex(3, 7)]

for num in nums:
    print(num ** 2)

выводит:

10000
0.09
4/25
0.09
(-40+42j)

Следует сказать, что оператор возведения в степень ** является правоассоциативным, то есть выражение x ** y ** z вычисляется справа налево и трактуется как x ** (y ** z), а не как (x ** y) ** z.

Приведенный ниже код:

print(2 ** 3 ** 4)     # 2 в 81-й степени
print((2 ** 3) ** 4)   # 8 в 4-й степени

выводит:

2417851639229258349412352
4096

Встроенная функция pow()

Встроенная функция pow() принимает два обязательных аргумента, первый из которых base — это число, которое необходимо возвести в определенную степень, а второй exp — собственно, сама степень.

Приведенный ниже код:

print(pow(2, 10))     # 2 в 10-й степени
print(pow(10, 2))     # 10 во 2-й степени

выводит:

1024
100

Следует отметить, что функция pow()⁣, как и оператор **, работает со всеми числовыми типами в Python, а также имеет аналогичную производительность, то есть выполняется примерно за такое же время.

Возникает резонный вопрос: зачем использовать данную функцию, когда есть простой и понятный оператор **? Дело в том, что функция pow() также может принимать необязательный аргумент mod, с помощью которого она возводит число в степень по модулю. Другими словами, она возвращает число, которое является остатком от деления результата возведения в степень на число mod.

Приведенный ниже код:

print(pow(2, 10, 15))     # 2 в 10-й степени по модулю 15
print(pow(10, 2, 15))     # 10 во 2-й степени по модулю 15

выводит:

4
10

Несложно заметить, что аналогичного результата можно добиться с использованием связки операторов ** и %.

Приведенный ниже код:

print(2 ** 10 % 15)      # 2 в 10-й степени по модулю 15
print(10 ** 2 % 15)      # 10 в 2-й степени по модулю 15

выводит:

4
10

И снова возникает вопрос: зачем использовать функцию pow(), если можно легко обойтись операторами ** и %? Оказывается, в отличие от оператора **, функция pow() при возведении числа в степень по модулю за кулисами использует оптимизированный алгоритм, который не вычисляет результат возведения в степень явно, как это делает оператор **.

Приведенный ниже код:

from timeit import timeit

power_by_stars = '''
number = 2
power = 10000
number ** power % 100
'''

power_by_pow = '''
number = 2
power = 10000
pow(number, power, 100)
'''

print('Возведение в степень по модулю с помощью оператора **:', timeit(power_by_stars, number=1_000_000), 'с.')
print('Возведение в степень по модулю с помощью функции pow():', timeit(power_by_pow, number=1_000_000), 'с.')

выводит (результат может незначительно отличаться):

Возведение в степень по модулю с помощью оператора **: 10.867430499754846 с.
Возведение в степень по модулю с помощью функции pow(): 0.17797810025513172 с.

В результате приведенного эксперимента получили, что функция pow() выполняет операцию возведения в степень по модулю намного быстрее операторов ** и %. Ее время выполнения примерно в 60 раз меньше времени выполнения связки операторов ** и %.

Возведение в степень по модулю — это операция, которая играет ключевую роль в областях, связанных с криптографией, теорией чисел, алгоритмами шифрования и безопасной передачей данных. Она лежит в основе алгоритма RSA, который используется для шифрования и дешифрования сообщений.

Исходный код функции pow() доступен по ссылке. Python использует алгоритм быстрого возведения в степень, схему "справа налево".

Функция math.pow()

Третий способ возведения в степень подразумевает использование функции pow() встроенного модуля math. Эта функция также принимает два аргумента: число и степень — и возвращает результат возведения числа в степень.

Приведенный ниже код:

import math

print(math.pow(2, 10))     # 2 в 10-й степени
print(math.pow(10, 2))     # 10 во 2-й степени

выводит:

1024.0
100.0

В отличие от оператора ** и встроенной функции pow(), функция math.pow() всегда неявно преобразует входные аргументы в вещественные числа (тип float) и соответственно возвращает результат в виде такого же вещественного числа.

Учитывая эту особенность, функцию math.pow() необходимо применять с осторожностью. Как известно, вещественные числа (тип float) имеют ограниченный диапазон значений в отличие от тех же целых чисел (тип int). Поэтому если результат возведения в степень окажется достаточно большим, то это приведет к возникновению ошибки переполнения.

Приведенный ниже код:

import math

print(math.pow(2, 10000))

приводит к возникновению ошибки переполнения:

OverflowError: math range error

Следует также отметить, что функция pow() встроенного модуля math не поддерживает возведение в степень комплексных чисел, поскольку не существует неявного преобразования из комплексного числа (тип complex) в вещественное (тип float).

Заключение

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

Например, оператор ** позволяет быстро выполнить возведение в степень для получения результата "на лету", а встроенная функция pow() позволяет выполнить возведение в степень по модулю. Функция math.pow() не является достаточно точной, однако она все равно может оказаться полезной при вычислении небольших степеней, особенно если результат необходим в виде вещественного числа.

А вы знали, что оператор ** как оператор возведения в степень впервые появился в языке программирования Fortran (Formula Translation)? Возможно, именно поэтому создатели Python решили использовать именно его вместо общепринятого символа каретки ^, который в Python отвечает за оператор побитового исключающего ИЛИ (XOR).

Присоединяйтесь к нашему телеграм-каналу, будет интересно и познавательно!

❤️Happy Pythoning!?

Комментарии (54)


  1. developerre
    25.06.2024 06:41
    +1

    Не знал про правоассоциативность оператора **. А почему решили сделать его таким?


    1. artptr86
      25.06.2024 06:41
      +11

      Потому что в математике возведение в степень правоассоциативно


      1. wyfinger
        25.06.2024 06:41
        +2

        Matlab, например, считает по другому: https://github.com/wyfinger/WEL-Script/wiki/Power-function-priority


    1. ToyJoy
      25.06.2024 06:41
      +3

      Возможно из за того что: (a^b)^c = a^(b*c)


  1. CrazyElf
    25.06.2024 06:41
    +4

    Если модуль не планируется брать, то следует выбирать оператор возведения в степень **. Потому что использование встроенной функции pow требует сначала найти её по имени, а потом уже вызвать. Вы можете вообще присвоить в имя pow что-то своё, так работает Питон. Поэтому сначала из переменной берётся ссылка на функцию, и потом эта функция вызывается. А оператор таких телодвижений не требует, он вызывается сразу. Хотя у разных интерпретаторов Питона могут быть разные оптимизации, где-то может и не будет разницы и код скомпилируется в одно и то же. Но по классике разница есть и какие-то доли процента производительности на этом можно выиграть. Очень рекомендую модуль dis, много интересного можно узнать, сравнивая похожий с виду код, во что он реально компилируется.


  1. printf
    25.06.2024 06:41

    общепринятого символа каретки ^

    Теперь общепринятый уже, видимо, **, т.к. Питон, Жаваскрипт и Руби. :)


    1. adeshere
      25.06.2024 06:41
      +11

      Теперь общепринятый уже, видимо, **, т.к. Питон, Жаваскрипт и Руби. :)

      Эх, молодежь ;-) Вообще-то в самых первых языках программирования, начиная с фортрана, было именно "**". Символ "^" стап использоваться заметно позже, и до сих пор уступает варианту "**" по количеству выбравших его "языков" (статья там длинная, см. подраздел "В языках программирования"):

      А "второй древнейший" так и вовсе только через библиотечную функцию... Ну и особенно весело с бейсиком: там разные диалекты использовали разные символы ;-)


      1. printf
        25.06.2024 06:41

        Думаю, оно из литературы "общепринятое", да.

        В лагере x ^ y еще Julia вроде.

        Вот молодежью меня давненько не величали, спасибо :з