一种计算任意大整数的整数平方根(isqrt)的有效算法

Siu*_*ji- 6 algorithm erlang biginteger bigint square-root

注意

对于Erlang或的解决方案C / C++,请转到下面的试验4.


维基百科文章

整数平方根

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

计算平方根的方法

  • 可以在这里找到做"魔术"的算法

[试验1:使用库功能]

isqrt(N) when erlang:is_integer(N), N >= 0 ->
    erlang:trunc(math:sqrt(N)).
Run Code Online (Sandbox Code Playgroud)

问题

此实现使用sqrt()C库中的函数,因此它不适用于任意大整数(请注意,返回的结果与输入不匹配.正确的答案应该是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> 
Run Code Online (Sandbox Code Playgroud)

[试验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).
Run Code Online (Sandbox Code Playgroud)

描述

此实现基于以下观察:

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
...
Run Code Online (Sandbox Code Playgroud)

问题

这个实现涉及bigint添加,所以我希望它能够快速运行.然而,当我用它喂它时1111111111111111111111111111111111111111 * 1111111111111111111111111111111111111111,它似乎永远在我(非常快)的机器上运行.


[试验3:使用带有BIGINT二进制搜索+1,-1并且div 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.
Run Code Online (Sandbox Code Playgroud)

变体2(修改上面的代码,以便边界与Mid + 1或Mid-1相反,参考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.
Run Code Online (Sandbox Code Playgroud)

问题

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


[试验4:使用Bigton +和Newton的方法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.
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

迭代变体

#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);
}
Run Code Online (Sandbox Code Playgroud)

问题

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


相关问题

python中的整数平方根

  • user448810答案使用"Newton's Method".我不确定使用"整数除法"进行除法是否合适.我稍后会尝试将其作为更新.[更新(2015-01-11):可以这样做]

  • 数学答案涉及使用第三方Python包gmpy,这对我来说不是很有利,因为我主要感兴趣的是只用内置设施在Erlang中解决它.

  • DSM回答似乎很有趣.我真的不明白发生了什么,但似乎那里有"魔法",所以它也不太适合我.

元整数平方根中的无限递归

  • 这个问题适用于C++,AraK(提问者)的算法看起来与上面的试验2的想法相同.

Vik*_*hat 3

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

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;
 }

}
Run Code Online (Sandbox Code Playgroud)

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

Log10(目标)如果需要计算:-

acc = target

log10 = 0;

while(acc>0) {

  log10 = log10 + 1;
  acc = acc/10;
}
Run Code Online (Sandbox Code Playgroud)

笔记 : 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);
Run Code Online (Sandbox Code Playgroud)