【问题标题】:Calculate Nth root with integer arithmetic用整数算术计算第 N 个根
【发布时间】:2012-02-08 06:06:40
【问题描述】:

有几种方法可以仅使用整数算术来找到整数平方根。例如this one。它使阅读变得有趣,同时也是一个非常有趣的理论,特别是对于我这一代,这些技术不再那么有用了。

主要是它不能使用浮点运算,因此排除了牛顿方法及其推导。我知道的唯一另一种求根的方法是二项式展开,但这也需要浮点运算。

有哪些技术/算法可以仅使用整数算术来计算整数 n 次根?

编辑:感谢到目前为止的所有答案。他们似乎都在进行稍微聪明的试验和改进。有没有更好的办法?

Edit2:好的,所以如果没有试验/改进以及牛顿法或二进制搜索,似乎没有聪明的方法可以做到这一点。谁能提供两者的比较理论上?我在两者之间运行了许多基准测试,发现它们非常相似。

【问题讨论】:

  • 您需要的输入值范围是多少?
  • @PaulR,理想情况下它可以是可扩展的,但我认为您现在可以假设基数和数字都是 32 位(无符号)整数。
  • 您允许哪些整数运算?平方根是一种特殊情况,因为可以仅使用加法、减法和移位来提取它们。
  • @Neil,我不想对其施加限制,因为这不适用于特定的应用程序,但我会说一个类似于 C 整数运算符列表的列表:加法,减法、乘法、(整数)除法、模运算和按位运算。 Ofc 速度始终是一个考虑因素,但不要太担心。
  • @Mat 通常在计算机上,我希望您会使用 2 作为基础,这似乎可以避免问题。

标签: algorithm math integer square-root


【解决方案1】:

我在 Excel 中的 VBA 中制作了算法。目前它只计算整数的根。实现小数也很容易。

只需将代码复制并粘贴到 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

【讨论】:

    【解决方案2】:

    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
    

    【讨论】:

    • “比什么更简单”?
    【解决方案3】:

    在我看来,Shifting nth root algorithm 提供的正是您想要的:

    移位第 n 根算法是一种用于提取正实数的第 n 根的算法,该算法通过将根数的 n 位从最高有效位开始移位迭代地进行,并在每次迭代中产生一位根,以类似于长除法的方式。

    链接的维基百科页面上有工作示例。

    【讨论】:

    • 来自维基百科页面:“当基数大于基数时,算法退化为二进制搜索”。我会看看是否可能使用(有效)十六进制而不是二进制来改进算法。
    【解决方案4】:

    一个简单的解决方案是使用二分搜索。

    假设我们正在寻找 x 的第 n 个根。

    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)
    

    【讨论】:

      【解决方案5】:

      一种明显的方法是将binary searchexponentiation by squaring 一起使用。这将允许您在O(log (x + n)) 中找到nthRoot(x, n):在[0, x] 中对最大整数k 进行二进制搜索,例如k^n &lt;= x。对于某些k,如果k^n &lt;= x,则将搜索缩减为[k + 1, x],否则将其缩减为[0, k]

      您需要更智能或更快的东西吗?

      【讨论】:

      • 我有兴趣看看是否有任何方法不涉及试用改进。虽然通过平方求幂是一个很好的发现谢谢,
      【解决方案6】:

      您可以只使用整数运算来使用牛顿法,步骤与浮点运算相同,只是您必须将浮点运算符替换为具有不同运算符的语言中相应的整数运算符。

      假设您要找到a &gt; 0 的第k 个整数根,它应该是最大整数r 使得r^k &lt;= 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 &lt;==&gt; step(k,a,x) &gt;= x

      【讨论】:

      • 再次查看 newton raphson 后,我发现有办法做到这一点,但它经常会达到在两个数字之间翻转的点(例如 15 的平方根在 3 和 4 之间翻转) .为了解决这个问题,完整的解决方案是here
      • 对于平方根,a = n*n-1n-1n 之间精确翻转。我不确定对于更高的功率是否有更多的值会导致翻转,但是每当这一步增加对根的近似值时——除了第一步,如果起点小于目标——你就完成了,较小的值是整数根。
      • 这与我得出的结论相同,这就是为什么我得出上述评论中发布的代码的原因。无论基数如何,翻转的值似乎总是在根的上方和下方,因此根位于这两个数字之间(因此它翻转)我的代码处理了这个问题。
      • 好的,所以看起来翻转比我以前想象的要复杂得多(取 7 的立方根,它在 1、2 和 3 之间翻转。由此我只能想象翻转介于 A 的第 n 个根的 n 个可能数之间。这给算法增加了很多复杂性。
      • 好的,我已经整理好了。 但是,由于使用x^(n-1),即使达到合理大小的数字,溢出也会成为一个大问题,因为即使是很小的值也会导致溢出。毫无疑问,这是任何使用幂的解决方案的限制,但肯定有一种方法不需要使用非常大的值。如果不是,那么这个解决方案非常有限,即使指数很低。
      猜你喜欢
      • 2014-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-26
      相关资源
      最近更新 更多