【发布时间】:2014-03-06 14:53:32
【问题描述】:
通知
如需Erlang 或C / C++ 中的解决方案,请转到下面的试用 4。
维基百科文章
- “整数平方根”的定义可以在这里找到
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、-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.
变体 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 位数字(即从 1000000000000000000000000000000000000000000000000000000000000 到 1000000000000000000000000000000000000000000000000000001000000)需要 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
相关问题
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]) < 1并且我们正在进行整数运算,因此x[k+1]必须等于x[k]才能满足条件。因此,当算法应用于输入3时:x0 = 3,x1 = (3 + 3 div 3) div 2 = 2,x2 = (2 + 3 div 2) div 2 = 1,x3 = (1 + 3 div 1) div 2 = 2,导致死循环。要更正此问题,请在x[k+1] >= x[k]时终止(由于您提到的原因)。 -
啊,你是对的。我正在查看第一个答案中的代码,它使用
x_{n_1} >= x_n作为停止条件。我认为可以更新 Wikipedia 示例以同时考虑整数和浮点除法。
标签: algorithm erlang biginteger bigint square-root