Вычислить корень N с целочисленной арифметикой

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

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

Какие существуют методы/алгоритмы для вычисления целых n-ых корней с использованием только целочисленной арифметики?

Изменить: спасибо за все ответы до сих пор. Все они кажутся немного более разумными пробами и улучшениями. Нет ли лучшего способа?

Edit2: Хорошо, похоже, нет умного способа сделать это без проб/улучшений и либо метода Ньютона, либо бинарного поиска. Кто-нибудь может сравнить их теоретически? Я провел ряд тестов между ними и нашел их очень похожими.


person Matt    schedule 11.01.2012    source источник
comment
Каков ваш требуемый диапазон входных значений?   -  person Paul R    schedule 12.01.2012
comment
@PaulR, в идеале это могло бы быть расширяемым, но я думаю, вы можете предположить, что и база, и число пока будут 32-битными (беззнаковыми) целыми числами.   -  person Matt    schedule 12.01.2012
comment
Какие целочисленные операции вы разрешаете? Квадратные корни — это особый случай, потому что их можно извлечь, используя только сложение, вычитание и сдвиги.   -  person Neil    schedule 12.01.2012
comment
@Neil, я не хочу накладывать на него ограничения, так как это не для конкретного приложения, но я бы сказал, что список похож, скажем, на список целочисленных операторов C: сложение, вычитание, умножение, (целочисленное) деление, модульные и побитовые операции. Скорость всегда важна, но не беспокойтесь об этом слишком сильно.   -  person Matt    schedule 12.01.2012
comment
В общем: в мире нет ничего, что можно было бы сделать с арифметикой с плавающей запятой и нельзя было бы сделать с целочисленной арифметикой почти одинаково. Хотя бы потому, что сама арифметика с плавающей запятой довольно легко реализуема с помощью целочисленной арифметики.   -  person Serge Dundich    schedule 12.01.2012
comment
@Mat В этом случае я бы выбрал алгоритм сдвига N-го корня в соответствии с ответом AakashM.   -  person Neil    schedule 13.01.2012
comment
@Neil, я занят, пытаясь реализовать этот алгоритм, чтобы сравнить его с моим существующим решением для метода Ньютона. К сожалению, для этого не так много кода в Интернете,   -  person Matt    schedule 13.01.2012
comment
@Neil, прочитайте мой комментарий, который я только что добавил к ответу AakashM, так как он показывает, почему я менее склонен использовать такой метод.   -  person Matt    schedule 13.01.2012
comment
@Mat Обычно на компьютере я ожидаю, что вы будете использовать 2 в качестве базы, что, казалось бы, позволит избежать проблемы.   -  person Neil    schedule 14.01.2012
comment
@ Нил, это не проблема, просто при работе с основанием 2 весь алгоритм фактически становится двоичным поиском, как и другие ответы на этот вопрос.   -  person Matt    schedule 14.01.2012
comment
@Matt — Алгоритм сдвига n-го корня эффективно становится бинарным поиском только тогда, когда основание больше, чем подкоренное число (например, вычисление кубического корня из 97 по основанию 100). ). В системе счисления по основанию 2 основание никогда не больше подкоренного, за исключением вырожденного случая, когда подкоренное число равно 1. В любом случае алгоритм сдвига n-го корня чрезвычайно эффективен в случае с основанием 2.   -  person Todd Lehman    schedule 26.10.2018


Ответы (6)


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

Допустим, вы хотите найти целое число k-го корня из a > 0, которое должно быть наибольшим целым числом r, таким что r^k <= a. Вы начинаете с любого положительного целого числа (конечно, хорошая отправная точка помогает).

int_type step(int_type k, int_type a, int_type x) {
    return ((k-1)*x + a/x^(k-1))/k;
}

int_type root(int_type k, int_type a) {
    int_type x = 1, y = step(k,a,x);
    do {
        x = y;
        y = step(k,a,x);
    }while(y < x);
    return x;
}

За исключением самого первого шага, у вас есть x == r <==> step(k,a,x) >= x.

person Daniel Fischer    schedule 11.01.2012
comment
Посмотрев еще раз на Ньютона Рафсона, я обнаружил, что есть способ сделать это, но очень часто он достигал точки, когда два числа переворачивались (например, квадратный корень из 15 переворачивался между 3 и 4). Чтобы противостоять этому, полное решение находится здесь - person Matt; 12.01.2012
comment
Для квадратных корней он переключается точно для a = n*n-1 между n-1 и n. Я не уверен, есть ли для более высоких степеней больше значений, которые приводят к перевороту, но всякий раз, когда шаг увеличивает приближение к корню - за исключением самого первого шага, если начальная точка была меньше, чем цель - все готово, меньшее значение является целым корнем. - person Daniel Fischer; 12.01.2012
comment
К такому же выводу я пришел, поэтому я пришел к коду, опубликованному в моем комментарии выше. Независимо от базы значения, которые переворачиваются, кажутся всегда выше и ниже корня, поэтому корень находится между этими двумя числами (поэтому почему он переворачивается), мой код имеет дело с этим. - person Matt; 12.01.2012
comment
Итак, оказалось, что переворот гораздо сложнее, чем я предполагал ранее (возьмем кубический корень из 7, и он перевернется между 1, 2 и 3. Исходя из этого, я могу только представить, что переворот происходит между n возможными числами для n-го числа). корень из A. Это сильно усложняет алгоритм. - person Matt; 12.01.2012
comment
Но это циклы 1 -> 3 -> 2 -> 1, 1 - это решение. Вам нужно только проверить, является ли step(x) < x. Если меньше, продолжайте, иначе x решение (за исключением первого шага, если начать с 5 для кубического корня из 511, получится 5 -> 10 -> 8 -> 7 -> 8; первое неинтересно, после этого в любое время step(x) >= x , готово. - person Daniel Fischer; 12.01.2012
comment
Хорошо, я разобрался. Однако может показаться, что переполнение будет огромной проблемой, поскольку вы достигнете даже разумных размеров из-за использования x^(n-1), поскольку это приведет к переполнению даже при малых значениях. Без сомнения, это ограничение любого решения, использующего мощности, но, безусловно, есть способ, который не требует использования очень больших значений. Если нет, то это решение очень ограничено, даже с низкими показателями. - person Matt; 13.01.2012
comment
@Daniel: я протестировал несколько формул, и эта кажется самой быстрой и точной, но у меня есть один вопрос: как можно переписать уравнение step(), чтобы разрешить k плавающие (не в форме 1 / n)? Например, pow(2, 0.12345) = 1.089337..., однако, если я установлю k на pow(0.12345, -1) = 8.100446..., я получу 1.090508... (при условии, что функция может принимать числа с плавающей запятой). - person Alix Axel; 07.05.2012
comment
@Matt по вашей ссылке возникли проблемы с обработкой 0 и 1. Я внес изменения, чтобы устранить эти проблемы. Взгляните: модифицированный - person timekeeper; 06.06.2016

Одним из очевидных способов может быть использование бинарного поиска вместе с возведение в степень путем возведения в квадрат. Это позволит вам найти nthRoot(x, n) в O(log (x + n)): бинарный поиск в [0, x] для наибольшего целого числа k такого, что k^n <= x. Для некоторых k, если k^n <= x, сократить поиск до [k + 1, x], иначе сократить до [0, k].

Вам нужно что-то умнее или быстрее?

person IVlad    schedule 11.01.2012
comment
Мне интересно посмотреть, есть ли какие-либо методы, не связанные с пробным улучшением. Хотя возведение в степень путем возведения в квадрат - хорошая находка, спасибо, - person Matt; 12.01.2012

Мне кажется, что алгоритм сдвига n-го корня обеспечивает именно то, что вам нужно:

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

На связанной странице википедии есть рабочие примеры.

person Community    schedule 12.01.2012
comment
Со страницы википедии: когда основание больше, чем подкоренное число, алгоритм вырождается в бинарный поиск. Я посмотрю, возможно ли, что работа с (эффективно) шестнадцатеричным, а не двоичным кодом улучшит алгоритм. - person Matt; 13.01.2012

Одним из простых решений является использование бинарного поиска.

Предположим, мы нашли корень n-й степени из x.

Function GetRange(x,n):
    y=1
    While y^n < x:
        y*2
    return (y/2,y)

Function BinSearch(a,b,x,):
    if a == b+1:
        if x-a^n < b^n - x:
           return a
        else:
           return b
    c = (a+b)/2
    if n< c^n:
        return BinSearch(a,c,x,n)
    else:
        return BinSearch(c,b,x,n)

a,b = GetRange(x,n)
print BinSearch(a,b,x,n)

===Быстрая версия===

Function BinSearch(a,b,x,):
    w1 = x-a^n
    w2 = b^n - x
    if a <= b+1:
        if w1 < w2:
           return a
        else:
           return b
    c = (w2*a+w1*b)/(w1+w2)
    if n< c^n:
        return BinSearch(a,c,x,n)
    else:
        return BinSearch(c,b,x,n)
person ElKamina    schedule 11.01.2012

Я сделал алгоритм в VBA в Excel. Пока он вычисляет только корни целых чисел. Также легко реализовать десятичные дроби.

Просто скопируйте и вставьте код в модуль EXCEL и введите имя функции в какую-нибудь ячейку, передав параметры.

Public Function RootShift(ByVal radicand As Double, degree As Long, Optional ByRef remainder As Double = 0) As Double

   Dim fullRadicand As String, partialRadicand As String, missingZeroes As Long, digit As Long

   Dim minimalPotency As Double, minimalRemainder As Double, potency As Double

   radicand = Int(radicand)

   degree = Abs(degree)

   fullRadicand = CStr(radicand)

   missingZeroes = degree - Len(fullRadicand) Mod degree

   If missingZeroes < degree Then

      fullRadicand = String(missingZeroes, "0") + fullRadicand

   End If

   remainder = 0

   RootShift = 0

   Do While fullRadicand <> ""

      partialRadicand = Left(fullRadicand, degree)

      fullRadicand = Mid(fullRadicand, degree + 1)

      minimalPotency = (RootShift * 10) ^ degree

      minimalRemainder = remainder * 10 ^ degree + Val(partialRadicand)

      For digit = 9 To 0 Step -1

          potency = (RootShift * 10 + digit) ^ degree - minimalPotency

          If potency <= minimalRemainder Then

             Exit For

          End If

      Next

      RootShift = RootShift * 10 + digit

      remainder = minimalRemainder - potency

   Loop

End Function
person João Rocha Labrego    schedule 21.05.2019

Алгоритм проще в VBA.

Public Function RootNth(radicand As Double, degree As Long) As Double
   Dim countDigits As Long, digit As Long, potency As Double
   Dim minDigit As Long, maxDigit As Long, partialRadicand As String
   Dim totalRadicand As String, remainder As Double

  radicand = Int(radicand)
  degree = Abs(degree)
  RootNth = 0
  partialRadicand = ""
  totalRadicand = CStr(radicand)
  countDigits = Len(totalRadicand) Mod degree
  countDigits = IIf(countDigits = 0, degree, countDigits)
  Do While totalRadicand <> ""
     partialRadicand = partialRadicand + Left(totalRadicand, countDigits)
     totalRadicand = Mid(totalRadicand, countDigits + 1)
     countDigits = degree
     minDigit = 0
     maxDigit = 9
     Do While minDigit <= maxDigit
        digit = Int((minDigit + maxDigit) / 2)
        potency = (RootNth * 10 + digit) ^ degree
        If potency = Val(partialRadicand) Then
           maxDigit = digit
           Exit Do
        End If
        If potency < Val(partialRadicand) Then
           minDigit = digit + 1
        Else
           maxDigit = digit - 1
        End If
     Loop
     RootNth = RootNth * 10 + maxDigit
  Loop
   End Function
person João Labrego    schedule 03.02.2015
comment
Проще чем что? - person Nathan Tuggy; 03.02.2015