平方根函数如何实现?


77

平方根函数如何实现?


15
如何在哪里实施?
fenomas

如果您有兴趣,《数字食谱》一书中有很多关于如何计算平方根,正弦和余弦,指数,对数等的信息。
菲利普·波特

3
@Matt:添加“ ...,但是这次尝试做出更好的猜测”,这实际上是一个准确的描述!
汤姆·安德森


Answers:


22

使用Binary Search和C ++轻松实现

double root(double n){
  // Max and min are used to take into account numbers less than 1
  double lo = min(1, n), hi = max(1, n), mid;

  // Update the bounds to be off the target by a factor of 10
  while(100 * lo * lo < n) lo *= 10;
  while(100 * hi * hi > n) hi *= 0.1;

  for(int i = 0 ; i < 100 ; i++){
      mid = (lo+hi)/2;
      if(mid*mid == n) return mid;
      if(mid*mid > n) hi = mid;
      else lo = mid;
  }
  return mid;
}

请注意,while循环是二进制搜索中最常见的方法,但我个人更喜欢for在处理十进制数字时使用它,它节省了一些特殊情况的处理,并从类似1000甚至什至这样的小循环中获得了非常准确的结果500(两者都会为几乎所有数字给出相同的结果但为了安全起见)。

编辑:查阅此Wikipedia文章,了解专门用于计算平方根的各种特殊方法。

编辑2:在输入小于1的情况下,应用@jorgbrown建议的更新来修复该功能。此外,应用优化以使目标根的界限减小10倍。


3
这比Newton-Raphson方法收敛慢。每次迭代增加1位精度,而NR使精度位数增加一倍。因此,如果您不介意“慢”,这是可行的。
乔纳森·莱夫勒

我可以问一下,每次迭代仅增加1位吗?而牛顿的方法加倍呢?
Amr Sabre

4
您的代码将每次迭代的搜索间隔减半,这基本上等于加1位。这有点粗略的近似,但是请计算迭代次数并看看。NR使用演算,可以更好地预测结果。在平方根中具有几位精度之后,它会非常迅速地收敛。请参阅Wikipedia Newton的方法-示例-平方根-或编写自己的平方根函数或使用首选搜索引擎的方法。
乔纳森·莱夫勒

1
当n小于1时,该例程根本不起作用。例如,对于1/4的平方根,该例程以lo = 0和hi = 1/4开始,但答案是1/2,不在0到1/4之间。因此,对于所有小于1的n,它总是返回n。在迭代1000次之后。您可以通过更改第一行来声明以下内容来解决此问题:将lo,hi和mid声明为double lo = 1, hi = n, mid; if (n < 1) lo = n, hi = 1;
jorgbrown

1
另外,如果有人说牛顿方法使用较少的循环,则可以指出牛顿方法使用除法,即15-80个周期,具体取决于所使用的CPU。另一方面,您的循环仅使用乘法和加法,每个循环仅几个周期。(要注意的是:您可能必须更改(lo+hi)/2(lo+hi)*0.5,具体取决于编译器,以确保它没有进行除法)
jorgbrown

9

在Intel硬件上,它通常在硬件SQRT指令之上实现。一些库只使用直接的结果,一些库可能会经过几轮牛顿优化,使其在极端情况下更加准确。


硬件sqrt指令是否对所有输入都准确(在数学上有效的情况下,在所有情况下均高达1ULP错误)
Paul Stelian,

@PaulStelian我相信是的。英特尔®64和IA-32架构软件开发人员手册的第4.8.4节通常讨论结果舍入,并说“舍入会导致结果中的错误最后一位少于一个单位”。第5.2.2节将平方根指令FSQRT列为“ x87 FPU基本算术指令”之一,因此我认为它属于该范围。第8.3.10节讨论了先验函数的准确性,但这意味着三角指令。
汤姆·安德森

1
@PaulStelian尽管请注意,此文档并不总是可信赖的
汤姆·安德森

8

FDLIBM(可自由分发的LIBM)具有很好的sqrt文档版本。e_sqrt.c

具有一个使用整数算术和递归公式一次修改一位的版本。

另一种方法使用牛顿法。它以黑魔法和查找表开始,以获取前8位,然后应用递归公式

 y_{i+1} = 1/2 * ( y_i + x / y_i)

x是我们开始的数字。这是苍鹭法的巴比伦法。它的历史可以追溯到公元一世纪的亚历山德拉英雄。

还有另一种方法称为快速逆平方根或reciproot。它使用一些“恶意浮点位级别黑客”来找到1 / sqrt(x)的值。i = 0x5f3759df - ( i >> 1 );它使用螳螂和指数利用浮点数的二进制表示形式。如果我们的数字x是(1 + m)* 2 ^ e,其中m是尾数,则e是指数,结果y = 1 / sqrt(x)=(1 + n)* 2 ^ f。记录日志

lg(y) = - 1/2 lg(x)
f + lg(1+n) = -1/2 e - 1/2 lg(1+m)

因此,我们看到结果的指数部分是数字的-1/2。黑魔法基本上对指数进行逐位移位,并对尾数使用线性近似。

一旦有了良好的一阶近似值,就可以使用牛顿方法获得更好的结果,最后可以进行一些位级别的工作来确定最后一位。


6

这是牛顿算法的一种实现,请参见https://tour.golang.org/flowcontrol/8

func Sqrt(x float64) float64 {
  // let initial guess to be 1
  z := 1.0
  for i := 1; i <= 10; i++ {
    z -= (z*z - x) / (2*z) // MAGIC LINE!!
    fmt.Println(z)
  }
  return z
}

以下是魔术线的数学解释。假设您想找到多项式$ f(x)= x ^ 2-a $的根。通过牛顿的方法,您可以从初始猜测$ x_0 = 1 $开始。下一个猜测是$ x_1 = x_0-f(x_0)/ f'(x_0)$,其中$ f'(x)= 2x $。因此,您的新猜测是

$ x_1 = x_0-(x_0 ^ 2-a)/ 2x_0 $


1
更具体地说,这是cs.wustl.edu/~kjg/CS101_SP97/Notes/SquareRoot/sqrt.html的更简洁的实现,之前已引用过它,即巴比伦/ Heron方法。另请参见en.wikipedia.org/wiki/…–
lima.sierra,

1

sqrt(); 幕后功能。

它始终检查图形中的中点。示例:sqrt(16)= 4; sqrt(4)= 2;

现在,如果您在16或4之内输入任何值,例如sqrt(10)==?

它找到2和4的中点,即= x,然后再次找到x和4的中点(不包括此输入的下限)。它一次又一次地重复此步骤,直到得到完美的答案,即sqrt(10)== 3.16227766017。它位于b / w 2和4。所有这些内置函数都是使用微积分,微分和积分创建的。


1

在Python中的实现: 根值的下限是该函数的输出。示例:8的平方根是2.82842 ...,此函数将给出输出'2'

def mySqrt(x):
        # return int(math.sqrt(x))
        if x==0 or x==1:
            return x
        else:
            start = 0
            end = x  
            while (start <= end):
                mid = int((start + end) / 2)
                if (mid*mid == x):
                    return mid
                elif (mid*mid < x):
                    start = mid + 1
                    ans = mid
                else:
                    end = mid - 1
            return ans

1

我也正在做一个sqrt函数,100000000次迭代需要14秒,与sqrt的1秒相比还是没什么

double mysqrt(double n)
{
    double x = n;
    int it = 4;
    if (n >= 90)
    {
        it = 6;
    }
    if (n >= 5000)
    {
        it = 8;
    }
    if (n >= 20000)
    {
        it = 10;
    }
    if (n >= 90000)
    {
        it = 11;
    }
    if (n >= 200000)
    {
        it = 12;
    }
    if (n >= 900000)
    {
        it = 13;
    }
    if (n >= 3000000)
    {
        it = 14;
    }
    if (n >= 10000000)
    {
        it = 15;
    }
    if (n >= 30000000)
    {
        it = 16;
    }
    if (n >= 100000000)
    {
        it = 17;
    }

    if (n >= 300000000)
    {
        it = 18;
    }
    if (n >= 1000000000)
    {
        it = 19;
    }

    for (int i = 0; i < it; i++)
    {
        x = 0.5*(x+n/x);
    }
    return x;
}

但是最快的实现是:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

float mysqrt(float n) {return 1/Q_rsqrt(n);}

1
Formula: root(number, <root>, <depth>) == number^(root^(-depth))

Usage: root(number,<root>,<depth>)

Example: root(16,2) == sqrt(16) == 4
Example: root(16,2,2) == sqrt(sqrt(16)) == 2
Example: root(64,3) == 4

Implementation in C#:

static double root(double number, double root = 2f, double depth = 1f)
{
    return Math.Pow(number, Math.Pow(root, -depth));
}

1
没有任何解释的代码转储很少有帮助。堆栈溢出只是学习,而不是提供摘要来盲目复制和粘贴。请编辑您的问题,并说明它比OP提供的效果更好(并且不要在您的帖子上签名)。
克里斯(Chris

1

到目前为止,解决方案主要是浮点运算,并且还假定除法指令可用且快速。

这是一个不使用FP或除法的简单直接的例程。除了第一条if语句,当输入较小时,该语句会加速例程,每一行都会计算结果的另一位。

constexpr unsigned int root(unsigned int x) {
  unsigned int i = 0;
  if (x >= 65536) {
    if ((i + 32768) * (i + 32768) <= x) i += 32768;
    if ((i + 16384) * (i + 16384) <= x) i += 16384;
    if ((i + 8192) * (i + 8192) <= x) i += 8192;
    if ((i + 4096) * (i + 4096) <= x) i += 4096;
    if ((i + 2048) * (i + 2048) <= x) i += 2048;
    if ((i + 1024) * (i + 1024) <= x) i += 1024;
    if ((i + 512) * (i + 512) <= x) i += 512;
    if ((i + 256) * (i + 256) <= x) i += 256;
  }
  if ((i + 128) * (i + 128) <= x) i += 128;
  if ((i + 64) * (i + 64) <= x) i += 64;
  if ((i + 32) * (i + 32) <= x) i += 32;
  if ((i + 16) * (i + 16) <= x) i += 16;
  if ((i + 8) * (i + 8) <= x) i += 8;
  if ((i + 4) * (i + 4) <= x) i += 4;
  if ((i + 2) * (i + 2) <= x) i += 2;
  if ((i + 1) * (i + 1) <= x) i += 1;
  return i;
}

0

要计算平方根(不使用内置math.sqrt函数):

SquareRootFunction.java

public class SquareRootFunction {

    public double squareRoot(double value,int decimalPoints)
    {
        int firstPart=0;


        /*calculating the integer part*/
        while(square(firstPart)<value)
        {
            firstPart++;            
        }

        if(square(firstPart)==value)
            return firstPart;
        firstPart--;

        /*calculating the decimal values*/
        double precisionVal=0.1;
        double[] decimalValues=new double[decimalPoints];
        double secondPart=0;

        for(int i=0;i<decimalPoints;i++)
        {
            while(square(firstPart+secondPart+decimalValues[i])<value)
            {
                decimalValues[i]+=precisionVal;
            }

            if(square(firstPart+secondPart+decimalValues[i])==value)
            {
                return (firstPart+secondPart+decimalValues[i]);
            }

            decimalValues[i]-=precisionVal;
            secondPart+=decimalValues[i];
            precisionVal*=0.1;
        }

        return(firstPart+secondPart);

    }


    public double square(double val)
    {
        return val*val;
    }

}

MainApp.java

import java.util.Scanner;

public class MainApp {

public static void main(String[] args) {

    double number;
    double result;
    int decimalPoints;
    Scanner in = new Scanner(System.in);

    SquareRootFunction sqrt=new SquareRootFunction();   
    System.out.println("Enter the number\n");               
    number=in.nextFloat();  

    System.out.println("Enter the decimal points\n");           
    decimalPoints=in.nextInt();

    result=sqrt.squareRoot(number,decimalPoints);

    System.out.println("The square root value is "+ result);

    in.close();

    }

}

例如,如果要计算1E36的平方根,则计算整数部分可能会很慢。实际上,int在达到正确值之前,它可能会溢出您的类型,不是吗。我不确定整个算法在找到1E-36的平方根上的效果如何。您可以调整指数-但范围通常为±300或更大,并且我认为您的代码在该范围的大部分中效果都不理想。
乔纳森·莱夫勒


0

因此,以防万一没有关于是否使用内置ceil或round函数的规范,这是Java中的一种递归方法,它使用Newton-Raphson方法查找无符号数的平方根。

public class FindSquareRoot {

    private static double newtonRaphson(double N, double X, double oldX) {

        if(N <= 0) return 0;

        if (Math.round(X) == Math.ceil(oldX))
            return X;

        return newtonRaphson(N, X - ((X * X) - N)/(2 * X), X);
    }

    //Driver method
    public static void main (String[] args) {
        System.out.println("Square root of 48.8: " + newtonRaphson(48.8, 10, 0));
    }
}

-1
long long int floorSqrt(long long int x) 
{
    long long r = 0;
    while((long)(1<<r)*(long)(1<<r) <= x){
        r++;
    }
    r--;
    long long b = r -1;
    long long ans = 1 << r;
    while(b >= 0){
        if(((long)(ans|1<<b)*(long)(ans|1<<b))<=x){
            ans |= (1<<b);
        }
        b--;
    }
    return ans;
}

-1

按照我在Golang中的解决方案。

package main

import (
   "fmt"
)

func Sqrt(x float64) float64 {
   z := 1.0 // initial guess to be 1
   i := 0
   for int(z*z) != int(x) { // until find the first approximation
      // Newton root algorithm
      z -= (z*z - x) / (2 * z)
      i++
   }
   return z
}

func main() {
   fmt.Println(Sqrt(8900009870))
}

遵循经典/通用解决方案。

package main

import (
"fmt"
"math"
)

func Sqrt(num float64) float64 {
   const DIFF = 0.0001 // To fix the precision
   z := 1.0

   for {
      z1 := z - (((z * z) - num) / (2 * z))
      // Return a result when the diff between the last execution 
      // and the current one is lass than the precision constant
      if (math.Abs(z1 - z) < DIFF) {
         break
      }
      z = z1
   }

   return z
}


func main() {
   fmt.Println(Sqrt(94339))
}

欲了解更多信息,请点击这里


-1

用法:root(数字,root,深度)

示例:root(16,2)== sqrt(16)== 4
示例:root(16,2,2)== sqrt(sqrt(16))== 2
示例:root(64,3)== 4

在C#中的实现

double root(double number, double root, double depth = 1f)
{
    return number ^ (root ^ (-depth));
}

用法:平方(深度)

范例:Sqrt(16)== 4
范例:Sqrt(8,2)== sqrt(sqrt(8))

double Sqrt(double number, double depth = 1) return root(number,2,depth);

创建人:Imk0tter


这实际上只是将平方根函数转换number为0.5。OP可能知道此身份,并对“如何计算number^ 0.5?”感兴趣。
weirdev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.