您的代码没有计算n 的平方根。它尝试计算已计算的√n 的连分数。我的意思是没关系,但是,如果它是正确的,你的方法更适合一般小数到有理数的转换。但是,对于常规(简单)连分数(所有分子都是 1)的 sqrt 函数,算法略有不同。
但是问题还没有结束。是的,通常情况下,√n 的 CF 系数采用重复回文的形式,以第一个非零系数的双倍结尾。如√31 =[5;1,1,3,5,3,1,1,10,1,1,3,5,3,1,1,10..]。现在没有简单的方法来查询每个给定n 的回文长度。有一些已知的模式,但它们远未定义所有n 的通用模式。所以在第一个回文结束时停止迭代是一种非常不确定的方法。想象一下
__
√226 =[15;30]
同时
____________________________________________________
√244 =[15;1,1,1,1,1,2,1,5,1,1,9,1,6,1,9,1,1,5,1,2,1,1,1,1,1,30]
如果您决定在大多数情况下在 2*f[0] 处停止迭代,您会得到一个像 √226 这样的错误近似值,或者像 √244 这样的一个过度计算的近似值。此外,一旦n 增长,追逐回文的终点就变得毫无意义,因为你永远不需要这样的精确度。
___________________________________________________________________________________________________________________________________________________________________________
√7114 = [84;2,1,9,3,1,10,2,23,1,1,1,1,1,2,1,27,2,1,1,3,1,2,1,1,1,16,4,3,1,3,2,1,6,18,1,1,2,6,11,11,6,2,1,1,18,6,1,2,3,1,3,4,16,1,1,1,2,1,3,1,1,2,27,1,2,1,1,1,1,1,23,2,10,1,3,9,1,2,168]
在这种情况下,一旦获得必要的精度就停止迭代是合理的。正如我在开头提到的,有两种方法。
- 通用的 Decimal to Rational 算法从任何十进制数中获取简单的连分数。这将使 CF 精确解析为该小数,而不会出现任何浮点错误。有关此算法的详细说明,您可以查看a previous answer of mine。
-
√n 恰好有一个更直接的算法,它与 1 基本相同,但针对平方根进行了调整。在这种情况下,您不提供√n,而是提供n。
思路如下。我们必须为在分子处包含平方根值的输入定义一个通用形式。然后我们尝试在连分数部分达到相同的表达式以便能够迭代。
让我们的输入格式为
q + √n
______
p
对于简单的平方根运算,我们可以假设q 是0 并且p 是1。如果我们可以在下一阶段建立这种形式,那么我们可以很容易地进行迭代。
从q = 0、p = 1、m是√n的整数部分和1/x是浮动部分的初始阶段开始,我们的目标是将x变成(q + √n) / p形式;
1 1 1 (√n + m) √n + m
√n = m + ___ ⇒ x = _______ ⇒ x = ________ . ________ ⇒ x = ________
x √n - m (√n - m) (√n + m) n - m^2
现在√n 是分子,我们的形式是;
√n + q
x = ______
p
q = m 和 p = n - m^2。在这一点上,您可以计算x 和下一个m 通过地板x。算法的广义形式变为;
√n + q 1 p p(√n - (q - pm)) p(√n + (pm - q))
x = ______ = m + ___ ⇒ x' = ______________ = _________________ = ________________
p x' √n + (q - pm) n - (q - pm)^2 n - (q - pm)^2
此时p 可以被n - (q - pm)^2 整除。现在这是稳定的,我们可以根据需要扩展它。让我们为q 和p 分配新的任务;
q' = pm-q;
p' = (n - q'^2)/p;
√n + q'
x' = ______
p'
m' = Math.floor(x')
请注意,当p' 变为 1 (n - q'^2 = p) 时,我们处于回文的末尾。然而,为了决定在哪里停止,我使用了与我的 toRational 算法中描述的机制相同的机制,正如上面备选方案 1 中所链接的那样。一旦达到 JS 浮点分辨率,它基本上就会停止。 JavaScript 代码如下;
function rationalSqrt(n){
var nr = Math.sqrt(n),
m = Math.floor(nr),
p = n-m**2,
q = m,
cs = [m],
n0 = 1,
d0 = 0,
n1 = m,
d1 = 1,
n2 = 0,
d2 = 1;
if (nr === m) return {n:m,d:1,cs};
while (Math.abs(nr-n2/d2) > Number.EPSILON){
m = Math.floor((nr+q)/p);
q = m*p-q;
p = (n-q**2)/p;
cs.push(m);
n2 = m*n1+n0;
d2 = m*d1+d0;
n0 = n1;
d0 = d1;
n1 = n2;
d1 = d2;
}
return {n:n2,d:d2,cs};
}
这两种算法略有不同。
- ~60% 的时间它们产生相同的分数。
- ~27% 的时间
toRational 在 JS 浮点分辨率内给出更小的分子和分母。
- ~13% 的时间
rationalSqrt(这个)在 JS 浮点分辨率内给出更小的分子和分母。
-
rationalSqrt 将得到精确的系数,就像人们期望从平方根得到的一样,但一旦分辨率足够,就会被截断。
-
toRational 给出了预期的 couse 系数,但最后一个可能与您对平方根系列的预期完全无关。
一个这样的例子是;
rationalSqrt(511); //returns
{ n : 882184734
, d : 39025555
, cs: [22,1,1,1,1,6,1,14,4,1,21,1,4,14,1,6,1]
}
同时
toRational(Math.sqrt(511));
{ n : 1215746799
, d : 53781472
, cs: [22,1,1,1,1,6,1,14,4,1,21,1,4,14,1,10]
}
进一步思考: 考虑给定 RCF 系数。我们可以将rationalSqrt 的算法倒转得到(q + √n) / p 的形式吗?这可能是一项有趣的任务。