Hàm căn bậc hai được thực hiện như thế nào? [đóng cửa]


79

Hàm căn bậc hai được thực hiện như thế nào?


17
Làm thế nào nó được thực hiện ở đâu?
fenomas,

Nếu bạn quan tâm, cuốn sách Bí quyết số có rất nhiều cách tính căn bậc hai, sin và cosin, hàm mũ, logarit, v.v.
Philip Potter

3
@Matt: thêm "... nhưng hãy thử đoán tốt hơn một chút lần này", và đó thực sự là một mô tả chính xác!
Tom Anderson,


Câu trả lời:


22

Thực hiện đơn giản bằng cách sử dụng Tìm kiếm nhị phân với 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;
}

Lưu ý rằng whilevòng lặp phổ biến nhất với tìm kiếm nhị phân nhưng cá nhân tôi thích sử dụng forkhi xử lý các số thập phân, nó tiết kiệm một số trường hợp đặc biệt xử lý và nhận được kết quả khá chính xác từ các vòng lặp nhỏ như vậy 1000hoặc thậm chí 500(Cả hai sẽ cho cùng một kết quả cho hầu hết các số nhưng chỉ để an toàn).

Chỉnh sửa: Xem bài viết Wikipedia này để biết các phương pháp-mục đích đặc biệt khác nhau chuyên tính toán căn bậc hai.

Chỉnh sửa 2: Áp dụng các bản cập nhật do @jorgbrown đề xuất để sửa chức năng trong trường hợp đầu vào ít hơn 1. Ngoài ra, hãy áp dụng tối ưu hóa để làm cho giới hạn gốc mục tiêu theo hệ số 10


3
Phương pháp này hội tụ chậm hơn so với phương pháp Newton-Raphson. Nó thêm 1 bit độ chính xác cho mỗi lần lặp, trong khi NR tăng gấp đôi số bit độ chính xác. Vì vậy, nếu bạn không ngại 'chậm', điều này hiệu quả.
Jonathan Leffler,

Tôi có thể hỏi làm thế nào điều này chỉ thêm 1 bit mỗi lần lặp? trong khi phương pháp của newton tăng gấp đôi nó?
Amr Sabre

4
Mã của bạn giảm một nửa khoảng thời gian được tìm kiếm trên mỗi lần lặp, về cơ bản tương đương với việc thêm 1 bit. Đó là một phép gần đúng hơi thô, nhưng hãy đếm số lần lặp lại và xem. NR sử dụng phép tính và thực hiện tốt hơn công việc dự đoán kết quả. Sau khi bạn có một vài bit độ chính xác trong căn bậc hai, nó sẽ hội tụ rất nhanh. Xem Phương pháp Newton của Wikipedia - Ví dụ - Căn bậc hai - hoặc SO về Viết hàm căn bậc hai của riêng bạn hoặc sử dụng công cụ tìm kiếm ưa thích của bạn.
Jonathan Leffler

1
Quy trình này hoàn toàn không hoạt động khi n nhỏ hơn 1. Ví dụ, đối với căn bậc hai của 1/4, quy trình bắt đầu với lo = 0 và hi = 1/4, nhưng câu trả lời là 1/2, không nằm trong khoảng từ 0 đến 1/4. Vì vậy, nó luôn trả về n, với mọi n nhỏ hơn 1. Sau khi lặp lại 1.000 lần. Bạn có thể khắc phục điều này bằng cách thay đổi dòng đầu tiên của bạn tuyên bố lo, hi, và giữa đượcdouble lo = 1, hi = n, mid; if (n < 1) lo = n, hi = 1;
jorgbrown

1
Ngoài ra, nếu ai đó nói rằng phương pháp của Newton sử dụng ít vòng lặp hơn, bạn có thể chỉ ra rằng phương pháp của Newton sử dụng phép chia, đó là 15-80 chu kỳ, tùy thuộc vào CPU nào được sử dụng. Mặt khác, vòng lặp của bạn chỉ sử dụng nhân và cộng, mỗi vòng chỉ có một vài chu kỳ. (Nhỏ lưu ý: bạn có thể phải thay đổi (lo+hi)/2để (lo+hi)*0.5, tùy thuộc vào trình biên dịch, chắc chắn nó không phải làm bộ phận)
jorgbrown

9

Trên phần cứng Intel, nó thường được triển khai trên đầu lệnh SQRT phần cứng. Một số thư viện chỉ sử dụng kết quả của việc đó, một số có thể đưa nó qua một vài vòng tối ưu hóa Newton để làm cho nó chính xác hơn trong các trường hợp góc.


Là hướng dẫn sqrt phần cứng chính xác cho tất cả các đầu vào (lên đến lỗi 1ULP trong tất cả các trường hợp về mặt toán học có một kết quả hợp lệ)
Paul Stelian

@PaulStelian Tôi tin là như vậy. Phần 4.8.4 của sách hướng dẫn dành cho nhà phát triển phần mềm kiến ​​trúc Intel® 64 và IA-32 nói về việc làm tròn kết quả nói chung và cho biết "Làm tròn gây ra lỗi trong kết quả nhỏ hơn một đơn vị ở vị trí cuối cùng". Phần 5.2.2 liệt kê lệnh căn bậc hai FSQRT là một trong "Hướng dẫn số học cơ bản FPU x87", vì vậy tôi tin rằng nó thuộc điều đó. Phần 8.3.10 nói về độ chính xác của các hàm siêu việt, nhưng điều đó có nghĩa là các hướng dẫn lượng giác.
Tom Anderson

1
@PaulStelian Mặc dù lưu ý rằng tài liệu về điều này không phải lúc nào cũng đáng tin cậy !
Tom Anderson

8

FDLIBM (LIBM có thể phân phối tự do) có một phiên bản sqrt được tài liệu hóa khá đẹp. e_sqrt.c .

Có một phiên bản sử dụng số học nguyên và công thức lặp lại sửa đổi từng bit một.

Một phương pháp khác sử dụng phương pháp của Newton. Nó bắt đầu với một số ma thuật đen và một bảng tra cứu để lấy 8 bit đầu tiên và sau đó áp dụng công thức lặp lại

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

trong đó x là số chúng ta bắt đầu với. Đây là phương pháp Heron của người Babylon . Nó có từ thời Anh hùng Alexandra vào giữa tháng Giêng sau Công nguyên.

Có một phương pháp khác được gọi là căn bậc hai nghịch đảo nhanh hoặc nghịch đảo . sử dụng một số "hack cấp độ bit dấu phẩy động độc ác" để tìm giá trị của 1 / sqrt (x). i = 0x5f3759df - ( i >> 1 );Nó khai thác biểu diễn nhị phân của một số float bằng cách sử dụng phần định trị và số mũ. Nếu số x của chúng ta là (1 + m) * 2 ^ e, trong đó m là phần định trị và e là số mũ và kết quả y = 1 / sqrt (x) = (1 + n) * 2 ^ f. Ghi nhật ký

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

Vì vậy, chúng tôi thấy phần số mũ của kết quả là -1/2 số mũ của số. Ma thuật đen về cơ bản thực hiện một sự dịch chuyển theo chiều dọc trên số mũ và sử dụng phép gần đúng tuyến tính trên phần định trị.

Khi bạn đã có giá trị gần đúng đầu tiên, bạn có thể sử dụng các phương pháp của Newton để có kết quả tốt hơn và cuối cùng, một số cấp độ bit có tác dụng sửa chữ số cuối cùng.


6

Đây là cách triển khai thuật toán của Newton, xem 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
}

Sau đây là một giải thích toán học của đường ma thuật. Giả sử bạn muốn tìm căn của đa thức $ f (x) = x ^ 2 - a $. Theo phương pháp của Newton, bạn có thể bắt đầu với dự đoán ban đầu $ x_0 = 1 $. Dự đoán tiếp theo là $ x_1 = x_0 - f (x_0) / f '(x_0) $, trong đó $ f' (x) = 2x $. Do đó, dự đoán mới của bạn là

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


1
Cụ thể hơn, đây là cách triển khai ngắn gọn hơn của cs.wustl.edu/~kjg/CS101_SP97/Notes/SquareRoot/sqrt.html , đã được tham chiếu trước đây, tức là phương pháp của Babylon / Heron. Xem thêm en.wikipedia.org/wiki/…
lima.sierra

1

sqrt (); chức năng Hậu trường.

Nó luôn kiểm tra các điểm giữa trong biểu đồ. Ví dụ: sqrt (16) = 4; sqrt (4) = 2;

Bây giờ nếu bạn cung cấp bất kỳ đầu vào nào bên trong 16 hoặc 4 như sqrt (10) ==?

Nó tìm thấy điểm giữa của 2 và 4 tức là = x, sau đó một lần nữa nó tìm thấy điểm giữa của x và 4 (Nó không bao gồm giới hạn dưới trong đầu vào này). Nó lặp đi lặp lại bước này cho đến khi nhận được câu trả lời hoàn hảo, tức là sqrt (10) == 3,16227766017. Nó nằm ở b / w 2 và 4. Tất cả hàm tích hợp này được tạo bằng cách sử dụng phép tính, phân biệt và tích hợp.


1

Thực thi bằng Python: Tầng của giá trị gốc là đầu ra của hàm này. Ví dụ: Căn bậc hai của 8 là 2,82842 ..., hàm này sẽ cho đầu ra '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

Tôi cũng đang tạo một hàm sqrt, 100000000 lần lặp mất 14 giây, vẫn chưa là gì so với 1 giây của sqrt

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

Nhưng cách triển khai nhanh nhất là:

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
Việc đổ mã mà không có bất kỳ lời giải thích nào hiếm khi hữu ích. Stack Overflow là về học tập, không cung cấp các đoạn mã để sao chép và dán một cách mù quáng. Vui lòng chỉnh sửa câu hỏi của bạn và giải thích cách nó hoạt động tốt hơn những gì OP cung cấp (và đừng ký tên vào bài viết của bạn).
Chris

1

Các giải pháp cho đến nay chủ yếu là dấu phẩy động ... và cũng đã giả định rằng lệnh chia có sẵn và nhanh chóng.

Đây là một quy trình đơn giản đơn giản không sử dụng FP hoặc chia. Mọi dòng tính toán một bit khác trong kết quả, ngoại trừ câu lệnh if đầu tiên tăng tốc quy trình khi đầu vào nhỏ.

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

Để tính căn bậc hai (mà không sử dụng hàm math.sqrt có sẵn):

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();

    }

}

Ví dụ, tính toán phần nguyên có vẻ chậm nếu bạn đang tính căn bậc hai của 1E36. Trên thực tế, nó có thể làm tràn intkiểu của bạn , phải không, trước khi nó đạt đến giá trị chính xác. Tôi cũng không chắc thuật toán nói chung sẽ hoạt động tốt như thế nào khi tìm căn bậc hai của 1E-36. Bạn có thể điều chỉnh số mũ - nhưng phạm vi thường là ± 300 hoặc lớn hơn và tôi không nghĩ rằng mã của bạn hoạt động tốt cho hầu hết phạm vi đó.
Jonathan Leffler

0

có một số thứ gọi là phương pháp Babylon.

static float squareRoot(float n)
{

    /*We are using n itself as 
    initial approximation This 
    can definitely be improved */
    float x = n;
    float y = 1;

    // e decides the accuracy level
    double e = 0.000001;
    while(x - y > e)
    {
        x = (x + y)/2;
        y = n/x;
    }
    return x;
}

liên kết để biết thêm thông tin: https://www.geeksforgeeks.org/square-root-of-a-perfect-square/


0

Vì vậy, đề phòng trường hợp không có thông số kỹ thuật nào về việc không nên sử dụng hàm ceil hay hàm tròn tích hợp sẵn, đây là một cách tiếp cận đệ quy trong Java để tìm căn bậc hai của một số không dấu bằng phương pháp 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

Theo giải pháp của tôi ở 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))
}

Theo một giải pháp cổ điển / phổ biến.

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

Để biết thêm thông tin kiểm tra tại đây


-1

Cách sử dụng : root (số, gốc, độ sâu)

Ví dụ : root (16,2) == sqrt (16) == 4
Ví dụ : root (16,2,2) == sqrt (sqrt (16)) == 2
Ví dụ : root (64,3) == 4

Thực hiện trong C # :

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

Cách sử dụng : Sqrt (Số, độ sâu)

Ví dụ : Sqrt (16) == 4
Ví dụ : Sqrt (8,2) == sqrt (sqrt (8))

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

Bởi: Imk0tter


Về cơ bản, điều này chỉ dịch hàm căn bậc hai để nâng numberlên 0,5. OP có thể đã biết về danh tính này và quan tâm đến "làm cách nào để tính number^ 0,5?"
weirdev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.