I may suggest four methods for solving your task. First is based on Binary Search. Second is based on Newton's Method. Third is based on Shifting n-th Root Algorithm. Fourth is called by me Chord-Tangent method described by me in picture here.
Binary Search was already implemented in many answers above. I just introduce here my own vision of it and its implementation.
As alternative I also implement Optimized Binary Search method (marked Opt). This method just starts from range [hi / 2, hi) where hi is equal to 2^(num_bit_length / k) if we're computing k-th root.
Newton's Method is new here, as I see it wasn't implemented in other answers. It is usually considered to be faster than Binary Search, although my own timings in code below don't show any speedup. Hence this method here is just for reference/interest.
Shifting Method is 30-50% faster than optimized binary search method, and should be even faster if implemented in C++, because C++ has fast 64 bit arithemtics which is partially used in this method.
Chord-Tangent Method:

Chord-Tangent Method is invented by me on piece of paper (see image above), it is inspired and is an improvement of Newton method. Basically I draw a Chord and a Tangent Line and find intersection with horizontal line y = n, these two intersections form lower and upper bound approximations of location of root solution (x0, n) where n = x0 ^ k. This method appeared to be fastest of all, while all other methods do more than 2000 iterations, this method does just 8 iterations, for the case of 8192-bit numbers. So this method is 200-300x times faster than previous (by speed) Shifting Method.
As an example I generate really huge random integer of 8192 bits in size. And measure timings of finding cubic root with both methods.
In test() function you can see that I passed k = 3 as root's power (cubic root), you can pass any power instead of 3.
Try it online!
def binary_search(begin, end, f, *, niter = [0]): while begin < end: niter[0] += 1 mid = (begin + end) >> 1 if f(mid): begin = mid + 1 else: end = mid return begin def binary_search_kth_root(n, k, *, verbose = False): # https://en.wikipedia.org/wiki/Binary_search_algorithm niter = [0] res = binary_search(0, n + 1, lambda root: root ** k < n, niter = niter) if verbose: print('Binary Search iterations:', niter[0]) return res def binary_search_opt_kth_root(n, k, *, verbose = False): # https://en.wikipedia.org/wiki/Binary_search_algorithm niter = [0] hi = 1 << (n.bit_length() // k - 1) while hi ** k <= n: niter[0] += 1 hi <<= 1 res = binary_search(hi >> 1, hi, lambda root: root ** k < n, niter = niter) if verbose: print('Binary Search Opt iterations:', niter[0]) return res def newton_kth_root(n, k, *, verbose = False): # https://en.wikipedia.org/wiki/Newton%27s_method f = lambda x: x ** k - n df = lambda x: k * x ** (k - 1) x, px, niter = n, 2 * n, [0] while abs(px - x) > 1: niter[0] += 1 px = x x -= f(x) // df(x) if verbose: print('Newton Method iterations:', niter[0]) mini, minv = None, None for i in range(-2, 3): v = abs(f(x + i)) if minv is None or v < minv: mini, minv = i, v return x + mini def shifting_kth_root(n, k, *, verbose = False): # https://en.wikipedia.org/wiki/Shifting_nth_root_algorithm B_bits = 64 r, y = 0, 0 B = 1 << B_bits Bk_bits = B_bits * k Bk_mask = (1 << Bk_bits) - 1 niter = [0] for i in range((n.bit_length() + Bk_bits - 1) // Bk_bits - 1, -1, -1): alpha = (n >> (i * Bk_bits)) & Bk_mask B_y = y << B_bits Bk_yk = (y ** k) << Bk_bits Bk_r_alpha = (r << Bk_bits) + alpha Bk_yk_Bk_r_alpha = Bk_yk + Bk_r_alpha beta = binary_search(1, B, lambda beta: (B_y + beta) ** k <= Bk_yk_Bk_r_alpha, niter = niter) - 1 y, r = B_y + beta, Bk_r_alpha - ((B_y + beta) ** k - Bk_yk) if verbose: print('Shifting Method iterations:', niter[0]) return y def chord_tangent_kth_root(n, k, *, verbose = False): niter = [0] hi = 1 << (n.bit_length() // k - 1) while hi ** k <= n: niter[0] += 1 hi <<= 1 f = lambda x: x ** k df = lambda x: k * x ** (k - 1) # https://i.sstatic.net/et9O0.jpg x_begin, x_end = hi >> 1, hi y_begin, y_end = f(x_begin), f(x_end) for icycle in range(1 << 30): if x_end - x_begin <= 1: break niter[0] += 1 if 0: # Do Binary Search step if needed x_mid = (x_begin + x_end) >> 1 y_mid = f(x_mid) if y_mid > n: x_end, y_end = x_mid, y_mid else: x_begin, y_begin = x_mid, y_mid # (y_end - y_begin) / (x_end - x_begin) = (n - y_begin) / (x_n - x_begin) -> x_n = x_begin + (n - y_begin) * (x_end - x_begin) // (y_end - y_begin) y_n = f(x_n) tangent_x = x_n + (n - y_n) // df(x_n) + 1 chord_x = x_n + (n - y_n) * (x_end - x_n) // (y_end - y_n) assert chord_x <= tangent_x, (chord_x, tangent_x) x_begin, x_end = chord_x, tangent_x y_begin, y_end = f(x_begin), f(x_end) assert y_begin <= n, (chord_x, y_begin, n, n - y_begin) assert y_end > n, (icycle, tangent_x - binary_search_kth_root(n, k), y_end, n, y_end - n) if verbose: print('Chord Tangent Method iterations:', niter[0]) return x_begin def test(): import random, timeit nruns = 3 bits = 8192 n = random.randrange(1 << (bits - 1), 1 << bits) a = binary_search_kth_root(n, 3, verbose = True) b = binary_search_opt_kth_root(n, 3, verbose = True) c = newton_kth_root(n, 3, verbose = True) d = shifting_kth_root(n, 3, verbose = True) e = chord_tangent_kth_root(n, 3, verbose = True) assert abs(a - b) <= 0 and abs(a - c) <= 1 and abs(a - d) <= 1 and abs(a - e) <= 1, (a - b, a - c, a - d, a - e) print() print('Binary Search timing:', round(timeit.timeit(lambda: binary_search_kth_root(n, 3), number = nruns) / nruns, 3), 'sec') print('Binary Search Opt timing:', round(timeit.timeit(lambda: binary_search_opt_kth_root(n, 3), number = nruns) / nruns, 3), 'sec') print('Newton Method timing:', round(timeit.timeit(lambda: newton_kth_root(n, 3), number = nruns) / nruns, 3), 'sec') print('Shifting Method timing:', round(timeit.timeit(lambda: shifting_kth_root(n, 3), number = nruns) / nruns, 3), 'sec') print('Chord Tangent Method timing:', round(timeit.timeit(lambda: chord_tangent_kth_root(n, 3), number = nruns) / nruns, 3), 'sec') if __name__ == '__main__': test()
Output:
Binary Search iterations: 8192 Binary Search Opt iterations: 2732 Newton Method iterations: 9348 Shifting Method iterations: 2752 Chord Tangent Method iterations: 8 Binary Search timing: 0.506 sec Binary Search Opt timing: 0.05 sec Newton Method timing: 2.09 sec Shifting Method timing: 0.03 sec Chord Tangent Method timing: 0.001 sec