【问题标题】:An efficient algorithm to calculate the integer square root (isqrt) of arbitrarily large integers一种计算任意大整数的整数平方根 (iqrt) 的有效算法
【发布时间】:2014-03-06 14:53:32
【问题描述】:

通知

如需ErlangC / C++ 中的解决方案,请转到下面的试用 4


维基百科文章

Integer square root

  • “整数平方根”的定义可以在这里找到

Methods of computing square roots

  • “位魔法”的算法可以在这里找到

[试用一:使用库函数]

代码

isqrt(N) when erlang:is_integer(N), N >= 0 ->
    erlang:trunc(math:sqrt(N)).

问题

此实现使用 C 库中的 sqrt() 函数,因此它不适用于任意大的整数(注意返回的结果与输入不匹配。正确答案应该是 12345678901234567890):

Erlang R16B03 (erts-5.10.4) [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
1> erlang:trunc(math:sqrt(12345678901234567890 * 12345678901234567890)).
12345678901234567168
2> 

[ 试用 2:仅使用 Bigint + ]

代码

isqrt2(N) when erlang:is_integer(N), N >= 0 ->
    isqrt2(N, 0, 3, 0).

isqrt2(N, I, _, Result) when I >= N ->
    Result;

isqrt2(N, I, Times, Result) ->
    isqrt2(N, I + Times, Times + 2, Result + 1).

说明

此实现基于以下观察:

isqrt(0) = 0   # <--- One 0
isqrt(1) = 1   # <-+
isqrt(2) = 1   #   |- Three 1's
isqrt(3) = 1   # <-+
isqrt(4) = 2   # <-+
isqrt(5) = 2   #   |
isqrt(6) = 2   #   |- Five 2's
isqrt(7) = 2   #   |
isqrt(8) = 2   # <-+
isqrt(9) = 3   # <-+
isqrt(10) = 3  #   |
isqrt(11) = 3  #   |
isqrt(12) = 3  #   |- Seven 3's
isqrt(13) = 3  #   |
isqrt(14) = 3  #   |
isqrt(15) = 3  # <-+
isqrt(16) = 4  # <--- Nine 4's
...

问题

此实现涉及 bigint 添加,因此我希望它运行得很快。然而,当我用1111111111111111111111111111111111111111 * 1111111111111111111111111111111111111111 喂它时,它似乎在我的(非常快的)机器上永远运行。


[ 试验 3:仅使用 Bigint +1-1div 2 的二进制搜索]

代码

变体 1(我的原始实现)

isqrt3(N) when erlang:is_integer(N), N >= 0 ->
    isqrt3(N, 1, N).

isqrt3(_N, Low, High) when High =:= Low + 1 ->
    Low;

isqrt3(N, Low, High) ->
    Mid = (Low + High) div 2,
    MidSqr = Mid * Mid,
    if
        %% This also catches N = 0 or 1
        MidSqr =:= N ->
            Mid;
        MidSqr < N ->
            isqrt3(N, Mid, High);
        MidSqr > N ->
            isqrt3(N, Low, Mid)
    end.

变体 2(修改上面的代码,使边界改为 Mid+1 或 Mid-1,参考 answer by Vikram Bhat

isqrt3a(N) when erlang:is_integer(N), N >= 0 ->
    isqrt3a(N, 1, N).

isqrt3a(N, Low, High) when Low >= High ->
    HighSqr = High * High,
    if
        HighSqr > N ->
            High - 1;
        HighSqr =< N ->
            High
    end;

isqrt3a(N, Low, High) ->
    Mid = (Low + High) div 2,
    MidSqr = Mid * Mid,
    if
        %% This also catches N = 0 or 1
        MidSqr =:= N ->
            Mid;
        MidSqr < N ->
            isqrt3a(N, Mid + 1, High);
        MidSqr > N ->
            isqrt3a(N, Low, Mid - 1)
    end.

问题

现在求解79位数字(即1111111111111111111111111111111111111111 * 1111111111111111111111111111111111111111)的闪电速度,结果立即显示。但是,在我的机器上解决一百万(1,000,000)个 61 位数字(即从 10000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000001000000)需要 60 秒(+- 2 秒)。我想做得更快。


[ 试验 4:使用牛顿法和 Bigint +div 仅限]

代码

isqrt4(0) -> 0;

isqrt4(N) when erlang:is_integer(N), N >= 0 ->
    isqrt4(N, N).

isqrt4(N, Xk) ->
    Xk1 = (Xk + N div Xk) div 2,
    if
        Xk1 >= Xk ->
            Xk;
        Xk1 < Xk ->
            isqrt4(N, Xk1)
    end.

C/C++ 代码(供您参考)

递归变体

#include <stdint.h>

uint32_t isqrt_impl(
    uint64_t const n,
    uint64_t const xk)
{
    uint64_t const xk1 = (xk + n / xk) / 2;
    return (xk1 >= xk) ? xk : isqrt_impl(n, xk1);
}

uint32_t isqrt(uint64_t const n)
{
    if (n == 0) return 0;
    if (n == 18446744073709551615ULL) return 4294967295U;
    return isqrt_impl(n, n);
}

迭代变体

#include <stdint.h>

uint32_t isqrt_iterative(uint64_t const n)
{
    uint64_t xk = n;
    if (n == 0) return 0;
    if (n == 18446744073709551615ULL) return 4294967295U;
    do
    {
        uint64_t const xk1 = (xk + n / xk) / 2;
        if (xk1 >= xk)
        {
            return xk;
        }
        else
        {
            xk = xk1;
        }
    } while (1);
}

问题

Erlang 代码在我的机器上在 40 秒(+- 1 秒)内解决了一百万 (1,000,000) 个 61 位数字,因此这比 试用 3 快。还能再快点吗?


关于我的机器

处理器: 3.4 GHz Intel Core i7

内存: 32 GB 1600 MHz DDR3

操作系统:Mac OS X 版本 10.9.1


相关问题

Integer square root in python

  • answer by user448810 使用 “牛顿法”。我不确定使用“整数除法”进行除法是否可以。我稍后会尝试这个作为更新。 [更新(2015-01-11):可以这样做]

  • 1234563 /li>
  • answer by DSM 看起来很有趣。我不是很明白这是怎么回事,但似乎其中涉及到“位魔法”,所以它也不太适合我。

Infinite Recursion in Meta Integer Square Root

  • 这个问题是针对 C++ 的,AraK(提问者)的算法看起来与上面的 Trial 2 的想法相同。

【问题讨论】:

  • // Python中的整数除法,所以牛顿对x^2 - n = 0的方法有效。
  • @Blender :我知道 // 在 Python 中的含义,因此我怀疑它是否有效,因为该算法发表在 Wikipedia 上(并且任何东西都与 "Newton's method") 适用于实数(注意浮点的脚注)。
  • 使用整数除法没有任何区别。序列 {x_n} 以isqrt(n) 的最大下限递减。
  • @Blender :直接应用 Wikipedia 中的算法并用整数除法代替除法不会导致正确的结果。由于 Wikipedia 的终止条件是 abs(x[k+1] - x[k]) &lt; 1 并且我们正在进行整数运算,因此 x[k+1] 必须等于 x[k] 才能满足条件。因此,当算法应用于输入3时:x0 = 3x1 = (3 + 3 div 3) div 2 = 2x2 = (2 + 3 div 2) div 2 = 1x3 = (1 + 3 div 1) div 2 = 2,导致死循环。要更正此问题,请在 x[k+1] &gt;= x[k] 时终止(由于您提到的原因)。
  • 啊,你是对的。我正在查看第一个答案中的代码,它使用x_{n_1} &gt;= x_n 作为停止条件。我认为可以更新 Wikipedia 示例以同时考虑整数和浮点除法。

标签: algorithm erlang biginteger bigint square-root


【解决方案1】:

像下面这样的二分搜索不需要浮点除法,只需要整数乘法(比牛顿法慢):-

low = 1;

/* More efficient bound

high = pow(10,log10(target)/2+1);

*/


high = target


while(low<high) {

 mid = (low+high)/2;
 currsq = mid*mid;

 if(currsq==target) {
    return(mid);
 }

 if(currsq<target) {

      if((mid+1)*(mid+1)>target) {
             return(mid);
      }    
      low =  mid+1;
  }

 else {

     high = mid-1;
 }

}

这适用于O(logN) 迭代,因此即使是非常大的数字也不应该永远运行

Log10(target) 计算(如果需要):-

acc = target

log10 = 0;

while(acc>0) {

  log10 = log10 + 1;
  acc = acc/10;
}

注意:acc/10是整数除法

编辑:-

有效限制:- sqrt(n) 的位数约为 n 的一半,因此您可以通过 high = 10^(log10(N)/2+1) && low = 10^(log10(N)/2-1) 获得更严格的限制,它应该提供 2 倍的速度起来。

评估界限:-

bound = 1;
acc = N;
count = 0;
while(acc>0) {

 acc = acc/10;

 if(count%2==0) {

    bound = bound*10;
 }

 count++;

}

high = bound*10;
low = bound/10;
isqrt(N,low,high);

【讨论】:

  • 我之前实现过这个。我会检查这个或牛顿的方法是否更好。问题是乘法是大整数,而不是机器整数,所以这是一个昂贵的操作。
  • @AsukaKenji-SiuChingPong- 整数乘法大约需要 O(logN^2),因此该算法的 O(logN^3) 对您的应用程序来说仍然相当快。
  • 能否解释一下high的初始值?在您的原始答案中,它是 target,您将其编辑了 2 次以成为 pow(10,log(target)/2+1)。为什么?此外,由于target 是一个大整数(比long long double 可以容纳的更大),恐怕你需要像ipow()ilog() 这样的东西来完成这项工作(这值得另一个问题),因为C 库无法处理。
  • @AsukaKenji-SiuChingPong- 您当然可以使用 high = target 但在这种情况下,您不必要地计算不能在目标 sqrt 范围内的值,因为 sqrt(target) 将有一半的位数作为目标,所以从 high = pow(10,log10(N)/2+1) 开始更有效,这比 high = target 提高了 2 倍速度
  • 好的,请让我把我的问题说清楚......我的意思是,你是如何达到初始值high = pow(10,log10(N)/2+1)的?这意味着:当 N = 10,高 = 10^(1.5) = 31;当 N = 100, 高 = 10^(2) = 100;当 N = 1000, 高 = 10^(2.5) = 316;当 N = 10000, 高 = 10^(3) = 1000;等等为什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-09
  • 2023-03-30
  • 2010-09-07
  • 2014-07-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多