3

I'm trying to solve this problem in C# using these 2 approaches:

public double NormalPowerMethod(double x, double toPower)
{
    return Math.Pow(x, toPower);
}

public double EfficientPowerMethod(double x, double toPower)
{
    return Math.Exp(Math.Log(x) * toPower);
}

I ran a simple test program :

EfficientPower ep = new EfficientPower();
Console.WriteLine("Enter x:");
double x = Double.Parse(Console.ReadLine());
Console.WriteLine("Enter power: ");
int toPower = Int32.Parse(Console.ReadLine());
Stopwatch timer = new Stopwatch();
timer.Start();
Console.WriteLine(string.Format("{0} to the power of {1}= {2}", x,toPower, ep.NormalPowerMethod(x,toPower)));
timer.Stop();
Console.WriteLine("Normal power time =" + timer.ElapsedTicks);

//-------------- efficient
timer.Reset();
timer.Start();
Console.WriteLine(string.Format("Efficient power: {0} to the power of {1}= {2}", x, toPower, ep.EfficientPowerMethod(x, toPower)));
timer.Stop();
Console.WriteLine("Efficient power time =" + timer.ElapsedTicks);

Console.ReadLine();

and I noticed :

  • Math.pow(x,y) is slower than Math.Exp(Math.log(x) * y)

For e.g:

Enter x:
15
Enter power:
26
Normal power: 15 to the power of 26= 3.78767524410635E+30
Normal power time =818
Efficient power: 15 to the power of 26= 3.78767524410634E+30
Efficient power time =533


Enter x:
1024
Enter power:
1024
Normal power: 1024 to the power of 1024= Infinity
Normal power time =810
Efficient power: 1024 to the power of 1024= Infinity
Efficient power time =519

Why such behaviour ? I thought more calculation will be slower ?

Dio Phung
  • 5,944
  • 5
  • 37
  • 55
  • I am not sure if your measurement is correct. You have to run this at least a few times to get decent measurements. – Patrick Hofman Jun 03 '14 at 09:23
  • 3
    One measurement is no measurement. – qqbenq Jun 03 '14 at 09:24
  • Take a look at this series - http://ericlippert.com/2013/05/14/benchmarking-mistakes-part-one/ – Daniel Kelley Jun 03 '14 at 09:25
  • I will include a proper test suite - but trust me, I have tried extreme cases : from pow(0,0) to extreme large pow(10240,10240) - the result is the same (well if the Stopwatch class in MSDN is trust-worthy) – Dio Phung Jun 03 '14 at 09:26
  • Hans answered your question I believe – Sriram Sakthivel Jun 03 '14 at 09:27
  • Cool, thanks @SriramSakthivel - I was trying to find the similar questions. Fair enough. – Dio Phung Jun 03 '14 at 09:29
  • I did a proper test and results show that efficient is a little more efficient than normal. If reopened, I will include test results and code. – Patrick Hofman Jun 03 '14 at 09:29
  • @PatrickHofman: do you mind forward your test case to phungkimcuong AT gmail DOT com ? I'm reading Hans' answer on the other topic and figuring out wth happened – Dio Phung Jun 03 '14 at 09:35
  • @SriramSakthivel : this question (http://stackoverflow.com/questions/8870442/how-is-math-pow-implemented-in-net-framework) is asking about "How to make it faster" , whereas I am asking about "Why is it faster" - I'm pretty sure which one is faster. Furthermore, Hans' answer did not justify "why" the latter method is faster. So please consider re-open my question, or please suggest how should I edit the question to avoid being marked as duplicate? – Dio Phung Jun 03 '14 at 10:03
  • I thought you'll get all the info you require over there. If not I'll reopen. I agree that is not a exact duplicate. I'll reopen in a moment. Sorry anyway.. – Sriram Sakthivel Jun 03 '14 at 10:05

3 Answers3

2

Here is the correct measurement script, since we already discusses your measurement is off:

static void Main(string[] args)
{
    DateTime start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        Math.Pow(10, 100);
    }

    TimeSpan diff = DateTime.Now - start;
    Console.WriteLine("Normal: {0:N0}", diff.TotalMilliseconds);

    //-------------- efficient
    start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        Math.Exp(Math.Log(10) * 100);
    }

    diff = DateTime.Now - start;
    Console.WriteLine("Efficient: {0:N0}", diff.TotalMilliseconds);

    Console.ReadLine();
}

The output with 10.000.000 tries (statistics are consistent after numerous tries):

Normal:    1.968 ms.
Efficient: 1.813 ms.

So 'efficient' performs about 8% better.

About the why:

According to Hans Passants answer:

It basically checks for corner cases, then calls the CRT's version of pow().

So there is some, though minor overhead.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Could you please add standard deviation to this? If ranges overlap then the real conclusion would be that they are roughly equally fast. – BartoszKP Jun 03 '14 at 10:16
2

Math.Pow has to do a few additional checks so it can return correct value if x < 0 and toPower is an integer.

Henrik
  • 23,186
  • 6
  • 42
  • 92
  • I hope you're right: From MSDN: Math.Pow: msdn.microsoft.com/en-us/library/system.math.pow(v=vs.110).aspx Math.Exp: msdn.microsoft.com/en-us/library/system.math.exp.aspx Math.Pow has a lot of checking whereas Math.Exp has none. But is there a way to confirm it? See David Hefferman doubt below - domain checking may not be the root cause. I'll perform a test again on the native pow() vs. exp() – Dio Phung Jun 04 '14 at 06:23
2

Hans's answer here (How is Math.Pow() implemented in .NET Framework?) explains that Math.Pow is not implemented in terms of Exp and Log. The implementation of Math.Pow(), once it has performed domain validation, calls the unmanaged CRT function pow(). It is not done that way, I presume, because the CRT pow() is more accurate than the Exp/Log expression.

So, the reason that Math.Pow is slower than the Exp/Log variant is that the former does a different calculation that takes longer. And the implementors chose that different calculation because it is more accurate than the Exp/Log version. In essence they preferred accuracy to efficiency.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @DioPhung I'm not convinced that the domain checking is the source of the perf difference. I suspect that a native `pow()` call is more expensive than a native `exp()/ln()` + mult version. That's quite easy to check. – David Heffernan Jun 03 '14 at 10:32