Improve performance custom square root algorithm C#

时间:2018-03-25 19:17:20

标签: c# algorithm performance

Here is a algorithm I have come up with to calculate the square root, currently when testing it with a loop n times and stopwatch it's about 20-100x slower then C# Math.Sqrt();

Is there any way to improve the performance of this function or is the performance as good as it gonna get with this specific algorithm?

My C# square root algorithm:

static class MyMath
{
    public static double Sqrt(double _d)
    {
        double x = 0;
        double y = 2;
        double z = 1;
        double w = _d;
        double h = 1;
        double t = 0;
        double px = 0;
        int itr = 0;
        while (true)
        {
            w = (w / y);
            h *= y;
            if (h > w)
            {
                t = w;
                w = h;
                h = t;
                z *= 0.5;
                y = (1 + z);
            }
            x = ((w + h) * 0.5);
            if (itr >= 100 || w == h || px == x)
            {
                return (x);
            }
            px = x;
            itr++;
        }
    }
}

How I test the performance:

using System.Diagnostics;


Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10000; i++)
{
    MyMath.Sqrt(2);
}
sw.Stop();
Debug.Print(sw.ElapsedTicks.ToString());

EDIT3: Slightly improved version:

    static class MyMath
    {
        public static double Sqrt(double _d)
        {
            double x = 0;
            double y = 2;
            double z = 1;
            double w = _d;
            double h = 1;
            double t = 0;
            double px = 1;
            while (true)
            {
                if (x == px)
                {
                    return ((w + h) * 0.5);
                }
                if (w < h)
                {
                    t = w;
                    w = h;
                    h = t;
                    z *= 0.25;
                    y = (z + 1);
                    px = x;
                }
                w /= y;
                h *= y;
                x = (w + h);
            }
        }
    }

EDIT3: Updated Slightly improved version2 + changed benchmark method2: (Running in Release Mode)

        Stopwatch sw = new Stopwatch();
        int n = 100000;
        double[] squareArr = new double[n];
        Random rng = new Random(1234);
        for (int i = 0; i < n; i++)
        {
            squareArr[i] = rng.Next(1, 100000);
        }
        sw.Start();
        for (int i = 0; i < n; i++)
        {
            squareArr[i] = MyMath.Sqrt(squareArr[i]) ;
        }
        sw.Stop();
        debugBox.AppendText("AverageTime: " + (sw.ElapsedTicks / (double)n).ToString());

Currently according to my test the default Math.Sqrt() ~0.086 Ticks and mySqrt() ~4.8 Ticks.

EDIT 4:(Fixed bug: moved px = x in if statement)

1 个答案:

答案 0 :(得分:0)

我创建了一个较新版本的算法,这个算法执行 ~0.97 Ticks vs Default Math.Sqrt ~0.086 所以仍然大约慢11倍,它不会在使用.ToString("G17")进行检查时,它总是具有完全的双精度,但它非常接近,但是现在它与sqrt(12544)是正确的,并且与算法的原始版本相比,它是一个相当好的改进,也因为算法收敛如果您无论何时需要用笔和纸计算平方根,那么答案真的很快,这是一个很好的算法。

(原始编辑)

static class MyMath
{
    public static double Sqrt(double _d)
    {
        double w = _d;
        double h = 1;
        double t = 0;
        if (h > w)
        {
            h = _d;
            w = 1;
        }
        while(true)
        {
            if (w < h)
            {
                break;
            }
            w *= 0.5;
            h += h;
        }
        for (int i = 0; i < 5; i++)
        {
            t = ((w + h) * 0.5);
            h = ((h / t) * w);
            w = t;
        }
        return (t);
    }
}

编辑 添加if (h > w)以便在将数字小于1时提高精度,并将循环数从6减少到5。

编辑2019: 稍微更新的版本快24% (然后我原来的编辑)

   public static double Sqrt(double _d)
   {
       double w = _d, h = 1, t = 0;
       if (w < 1)
       {
           h = _d;
           w = 1;
       }
       do
       {
           w *= 0.5;
           h += h;
       } while (w > h);
       for (int i = 0; i < 4; i++)
       {
           t = ((w + h) * 0.5);
           h = ((h / t) * w);
           w = t;
       }
       return (((w + h) * 0.5));
   }

我的最快版本,快51% (然后是我原来的编辑),总共只使用两个分区,但不是干净的代码:( Math.Sqrt()仍然快〜4倍)

  public static double Sqrt(double _d) 
  {
      double w = _d, h = 1;

      if (w < 1)
      {
          h = _d;
          w = 1;
      }

      do
      {
          w *= 0.5;
          h += h;
      } while (w > h);

      double x = h + w;
      double x2 = x * x;
      double x4 = x2 * x * x;
      double x6 = x4 * x * x;
      double x8 = x6 * x * x;
      double h2 = h * h;
      double h3 = h2 * h;
      double h4 = h3 * h;
      double w2 = w * w;
      double w3 = w2 * w;
      double w4 = w3 * w;
      double hw = h * w;
      double h2w2 = h2 * w2;
      double a = (256 * h4 * w4 + 1792 * h3 * w3 * x2 + 1120 * h2w2 * x4 + 112 * hw * x6 + x8);
      double b = (16 * h2w2 + 24 * hw * x2 + x4);
      double c = (4 * hw + x2);
      double xcb = x * c * b;
      return (8 * hw * xcb) / a + a / (32 * xcb);
  }

编辑2 2019:

这是我创建的一种新算法,它通过y = x^2 (num to sqrt)的负偏移"a"图形工作,然后x轴相交图形是精确的平方根,算法试图通过在最小最大边界之间创建一条线来找到该交互点,然后从该线与x.axis相交的位置创建两个新的最大最大边界并重复,它将非常快速地收敛在完全双精度平方根,通常在10次迭代。 这是一个紧凑的算法,只需很少的迭代即可收敛到非常高的精度,但遗憾的是它比我的其他算法慢一点(慢10倍),但我仍然认为这是一个有趣的算法。

public static double Sqrt3(double a)
{
    double b = a + 1, c = 0, d = -a, e = b * b, f;
    g: f = (c * e - d * b) / (e - d);
    if (double.IsNaN(f)) return c;
    d = f * f - a;
    b = (f + c) * 0.5;
    e = b * b - a;
    c = f;
    goto g;
}
/*
    Sqrt(3)
    0 : 0.631578947368421
    1 : 3.37719298245614
    2 : 1.81530333576402
    3 : 1.74835949499074
    4 : 1.73228078277221
    5 : 1.7320513552106
    6 : 1.7320508075871
    7 : 1.73205080756888
    8 : 1.73205080756888
    9 : 1.73205080756888
    1.7320508075688772 : My.Sqrt() .ToString("G17")
    1.7320508075688772 : Math.Sqrt() .ToString("G17")
*/