В чем разница между разными способами извлечь корень в 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): ...Речь в первую очередь идёт о корректности (точности вычислений). Производительность на втором месте.
К сожалению, в 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 Войдите чтобы оставить ответ