0

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)

Patrik Fröhler
  • 1,221
  • 1
  • 11
  • 38
  • Compile in release mode, but you'll have to change your test otherwise it will be optimized out. – Ron Beyer Mar 25 '18 at 19:20
  • Would you accept less accuracy in the result to achieve reduced computation times? – Andrew Morton Mar 25 '18 at 19:22
  • How should I change the test so I't do't compile out and I don't introduce other unpredictable large latency, example If I change it to sqrt(random num) then put that in an array and in the end print out the array, I would presume it wouldn't get optimized out then but the latency from the random gen and placing array would make it difficult to determent the performance of the square, maybe there is a better way then that? @RonBeyer – Patrik Fröhler Mar 25 '18 at 19:29
  • The accuracy need to be the same as standard Math.Sqrt(); @AndrewMorton – Patrik Fröhler Mar 25 '18 at 19:30
  • Generate the array ahead of time then store the results in a new array or list. I would just time the method call and add the times to a list to extract the min, max, and average times. – Ron Beyer Mar 25 '18 at 19:31
  • 8
    So why not use `Math.Sqrt`? Why are you trying to reinvent the wheel? If there was a faster way, `Math.Sqrt` would be doing it... – Richardissimo Mar 25 '18 at 19:46
  • The purpose is to see if there is any optimization I haven't tough about and if it would be possible to rewrite the class in some way to improve the performance, and if that's the case it would be useful information when making other functions in the future, also I was doing some math and I happened to come up with this square root algorithm that allows me to estimate the square root of any number pretty quickly in my head, so thought It would be interesting to see how it performed in code. @Richardissimo – Patrik Fröhler Mar 25 '18 at 19:56
  • 4
    This question would be a better fit on https://codereview.stackexchange.com/ – xxbbcc Mar 25 '18 at 20:41
  • 1
    `Math.Sqrt` is internally implemented in native C++, which is far more performant than C#, and is much more optimized than you think. – meowgoesthedog Mar 25 '18 at 21:23
  • see [Faster Alternative to Math.sqrt](https://stackoverflow.com/a/41415008/2521214) and the sublinks. Simple binary search on the double is just 63 iterations of a simple loop containing one floating multiplication and comparison all the rest is just bit and integer operations... btw sqrt is implemented on the FPU so do not expect CPU code will be faster than that – Spektre Mar 26 '18 at 11:32
  • Your test is missing the crucial step of evaluating whether its results are the same as `Math.Sqrt`, since you're interested in achieving the same accuracy. (Hint: they're not.) Also, since you're microbenchmarking, consider [benchmarkdotnet](http://benchmarkdotnet.org/) to avoid any pitfalls with measuring time you are (not) supposed to be measuring. – Jeroen Mostert Mar 26 '18 at 15:32
  • mySqrt(2) = 1,4142135623731 Math.Sqrt(2) = 1,4142135623731 @JeroenMostert Why don't you think they have the same accuracy? – Patrik Fröhler Mar 26 '18 at 15:36
  • `mySqrt(12544) != Math.Sqrt(12544)`. The latter returns `112`, exactly. This is one of the few cases where floating-point comparison with `==` is OK. – Jeroen Mostert Mar 26 '18 at 15:37
  • Both Math.Sqrt(12544) and mySqrt(12544) returns 112, what do you mean? @JeroenMostert – Patrik Fröhler Mar 26 '18 at 15:39
  • 2
    `mySqrt(12544).ToString("G17") == 111.99999999999999`. Do not be fooled by the default string representation, which rounds. – Jeroen Mostert Mar 26 '18 at 15:41
  • That's interesting, yeah looks like a slight precision error. – Patrik Fröhler Mar 26 '18 at 15:44
  • 2
    Your method is much slower than Math.Sqrt, and this is expected. On modern PCs, sqrt takes approximately same time as a single division, e.g. on Intel Haswell, FDIV instruction takes 10-24 cycles, FSQRT instruction 10-23 cycles. There’s another floating point division instruction divpd but it’s only slightly faster, 10-20 cycles. http://www.agner.org/optimize/instruction_tables.pdf And you have many divisions in your loop. – Soonts Mar 28 '18 at 04:54

1 Answers1

0

I created a newer version of the algorithm, this one performs ~0.97 Ticks vs Default Math.Sqrt ~0.086 so still about ~11x slower and it don't always have the full double precision when checking with .ToString("G17"), but its very close, however now it is correct with sqrt(12544) and compared to the original version of the algorithm its a pretty good improvement, also because the algorithm converges really quickly at the answer if you would for wherever reason have to calculate the square root with pen and paper, it's a nice algorithm to use.

(original edit)

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

EDIT: Added if (h > w) to improve precision when squaring numbers less then 1, and reduced for loop from 6 to 5 to.

EDIT 2019: A slightly updated version it is 24% faster (then my original edit):

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

My fastest version yet, it is 51% faster (then my original edit), only uses two divisions total, but not as clean code: (Math.Sqrt() is still ~4x faster)

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

EDIT 2 2019 :

Here is a new algorithm that I have created, it works by negative offsetting y = x^2 graph by "a"(the num to sqrt) then where the x-axis intersects the graph is the exact square root, and the algorithm tries to find that interaction point by creating a line between min max bounds then from where that line intersects the x.axis it creates two new min max bounds and repeat, it will very quickly converge at the full double precision square root, usually within 10 iterations. It's a compact algorithm that converges with very few iteration to a very high precision, but unfortunately its a fair bit slower (10x slower) then my other algorithm, however I still think it's an interesting algorithm.

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")
*/
Patrik Fröhler
  • 1,221
  • 1
  • 11
  • 38
  • Sorry for necroing this, but I had to add an inverter, for cases when negative numbers are passed, because that do-while loop don't resolve, so before it I invert the w with a -w if it is < 0. – Zorkind Apr 08 '23 at 17:10