Avatar ·

В чем разница между разными способами извлечь корень в Python?

📁 python, пример

Извлечь корень в Питоне можно тремя способами x ** 0.5, pow(x, 0.5), math.sqrt(x). А для целых с целым результатом ещё и math.isqrt(x). Какая между ними разница?

P.S. Например при проверке простоты n нужно сделать цикл до корня квадратного из n. Так правильно?

for i in range(2, int(n ** 0.5) + 1):    ...

Речь в первую очередь идёт о корректности (точности вычислений). Производительность на втором месте.

Avatar ·

К сожалению, в Google Colab нет функции math.isqrt, потому что там Python 3.7.11. А насчёт остальных функций можно проверить скорость так (небольшой цикл добавлен, чтобы не было кэширования, а то без цикла %timeit ругается, что возможно результаты были закэшированы, хотя они примерно так же соотносятся, я проверял, но лучше принять меры предосторожности):

import mathn = 100%timeit [x ** 0.5 for x in range(n)]%timeit [pow(x, 0.5) for x in range(n)]%timeit [math.sqrt(x) for x in range(n)]

Вывод:

100000 loops, best of 5: 17.7 µs per loop10000 loops, best of 5: 23.9 µs per loop100000 loops, best of 5: 13.2 µs per loop

Самая быстрая - специализированная функция math.sqrt, что логично. Оператор ** чуть более медленный, а функция pow самая медленная, что, в общем, давно известно. Функцию pow имеет смысл применять только с третьим аргументом (остаток по модулю), тогда её использование может быть оправдано.
Также проверим, а выдают ли эти варианты одинаковые результаты. Хотя бы на каком-то списке натуральных чисел:

import mathn = 10000y1 = [x ** 0.5 for x in range(n)]y2 = [pow(x, 0.5) for x in range(n)]y3 = [math.sqrt(x) for x in range(n)]print(y1 == y2 == y3, sum(y1) == sum(y2) == sum(y3))

Вывод:

True True

Ещё можно посмотреть в какой код переводятся эти варианты и убедиться, что код разный:

dis.dis('x ** 0.5')  1           0 LOAD_NAME                0 (x)              2 LOAD_CONST               0 (0.5)              4 BINARY_POWER              6 RETU_VALUEdis.dis('pow(x, 0.5)')  1           0 LOAD_NAME                0 (pow)              2 LOAD_NAME                1 (x)              4 LOAD_CONST               0 (0.5)              6 CALL_FUNCTION            2              8 RETU_VALUEdis.dis('math.sqrt(x)')  1           0 LOAD_NAME                0 (math)              2 LOAD_METHOD              1 (sqrt)              4 LOAD_NAME                2 (x)              6 CALL_METHOD              1              8 RETU_VALUE

Если скорость важна, то для извлечения корня видимо нужно использовать специально заточенную под это функцию math.sqrt. В общем же случае проще на все случаи жизни использовать оператор **. И в случае, когда нас интересует вариант с возведением в степень и дальнейшим взятием остатка по модулю - тут лучше всего функция pow.

P.S. Ну и, конечно, если нужно получить сразу много корней, то лучше использовать векторизованный Numpy для этого. Использование для задачи range/sqrt/sum векторизированных функций даёт 50-кратное ускорение:

import mathimport numpy as npn = 1000000%timeit sum_py = sum(math.sqrt(x) for x in range(n))%timeit sum_nu = np.sum(np.sqrt(np.arange(n)))

Вывод:

10 loops, best of 5: 157 ms per loop100 loops, best of 5: 3.43 ms per loop

Проверка, что результат сошёлся (в данном случае - приблизительно):

import mathimport numpy as npn = 1000000sum_py = sum(math.sqrt(x) for x in range(n))sum_nu = np.sum(np.sqrt(np.arange(n)))print(sum_py, abs(sum_py-sum_nu))

Вывод:

666666166.4588418 1.9550323486328125e-05

Войдите чтобы оставить ответ

Блоги