views:

247

answers:

6
float f = 5.13;
double d = 5.13;

float fp = f - (float)Math.floor(f);
double dp = d - Math.floor(d);

Isn't there any faster way than calling an external function every time?

+5  A: 

"External function"?

System.Math is built into mscorlib!

This is actually the fastest way to do this.

MiffTheFox
@MiffTheFox: No it isn't. My answer is faster...and just as clear. :P
Brian
Mine's faster as well.
Michael
@Michael: I suspect yours is faster than mine, but feel no urge to check. But yours works with floats, too. And is still relatively clear.
Brian
A: 

It is static so this should be really fast, there is no object to stand up. You can always to bit level math, but unless you have some serious use the function. Likely the floor() method is already doing this, but you could inline it and cut out checks etc if you need something really fast, but in C# this is not your greatest performance issue.

Ted Johnson
+2  A: 

Well, I doubt you'll get any real world performance gain, but according to Reflector Math.Floor is this:

public static decimal Floor(decimal d)
{
    return decimal.Floor(d);
}

So arguably

double dp = d - decimal.Floor(d);

may be quicker. (Compiler optimisations make the whole point moot I know...)


For those who may be interested to take this to its logical conclusion decimal.Floor is:

public static decimal Floor(decimal d)
{
    decimal result = 0M;
    FCallFloor(ref result, d);
    return result;
}

with FCallFloor being a invoke to unmanaged code, so you are pretty much at the limit of the "optimisation" there.

Martin Harris
+3  A: 

You could cast f to an int which would trim the fractional part. This presumes that your doubles fall within the range of an integer.

Of course, the jitter may be smart enough to optimize Math.floor to some inlined asm that'll do the floor, which may be faster than the cast to int then cast back to float.

Have you actually measured and verified that the performance of Math.floor is affecting your program? If you haven't, you shouldn't bother with this level of micro-optimization until you know that is a problem, and then measure the performance of this alternative against the original code.

EDIT: This does appear faster. The following code takes 717ms when using Math.Floor(), and 172 ms for the int casting code on my machine, in release mode. But again, I doubt the perf improvement really matters - to get this to be measurable I had to do 100m iterations. Also, I find Math.Floor() to be much more readable and obvious what the intent is, and a future CLR could emit more optimal code for Math.Floor and beat out this approach easily.

    private static double Floor1Test()
    {
        // Keep track of results in total so ops aren't optimized away.
        double total = 0;
        for (int i = 0; i < 100000000; i++)
        {
            float f = 5.13f;
            double d = 5.13;
            float fp = f - (float)Math.Floor(f);
            double dp = d - (float)Math.Floor(d);
            total = fp + dp;
        }

        return total;
    }

    private static double Floor2Test()
    {
        // Keep track of total so ops aren't optimized away.
        double total = 0;
        for (int i = 0; i < 100000000; i++)
        {
            float f = 5.13f;
            double d = 5.13;
            float fp = f - (int)(f);
            double dp = d - (int)(d);
            total = fp + dp;
        }

        return total;
    }

    static void Main(string[] args)
    {
        System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();

        // Unused run first, guarantee code is JIT'd.
        timer.Start();
        Floor1Test();
        Floor2Test();
        timer.Stop();

        timer.Reset();
        timer.Start();
        Floor1Test();
        timer.Stop();
        long floor1time = timer.ElapsedMilliseconds;

        timer.Reset();
        timer.Start();
        Floor2Test();
        timer.Stop();

        long floor2time = timer.ElapsedMilliseconds;

        Console.WriteLine("Floor 1 - {0} ms", floor1time);
        Console.WriteLine("Floor 2 - {0} ms", floor2time);
    }
}
Michael
It's theoretically possible for your two approaches to get different results. As a result, it is possible that, even if the people writing the CLR consider the optimization you describe, they may reject it due to weird issues that most (but not all) people don't care about.
Brian
float fp = f - (int)(f) gives the wrong answer at least some of the time, such as when f = 5.13f.
Grant Wagner
@Brian - Sure, I just wanted to call out that just because the current code-gen for Math.Floor is sub-par, it could easily be improved in the future.
Michael
@Grant - I prefer "less-precise" than wrong when dealing with floating point like this :) Mapping floats to base-ten integers always has precision issues.
Michael
@Grant: float fp = f - (int)(f) is not giving a wrong answer. Floats are in base 2. You are assuming 5.13f means 5.13M, but it does not. That a normal user assumes it will is the user's mistake. If the user expected to be working with a floating point decimal number, they'd be using decimal.
Brian
I added a 3rd function for only the loop itself to remove the time it takes for the other loop elements, and got these results (Release version): Loops - 34 ms, Floor 1 - 620 ms, Floor 2 - 197 ms. So it clearly shows that the int method is 3.14 times faster.
manixrock
Casting to int doesn't work if the double is outside the range of int.
Eric Lippert
@Eric - Updated answer. I thought about it, but guessed noone would notice :)
Michael
+3  A: 

Donald E. Knuth said:

"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil."

So unless you have benchmarked your application and found positive evidence that this operations is the bottleneck, then don't bother optimizing these this line of code.

Martin Geisler
+2  A: 

In the case of Decimal, I would recommend ignoring everyone yelling not to change it and try using Decimal.Truncate. Whether it is faster or not, it is a function specifically intended for what you are trying to do and thus is a bit clearer.

Oh, and by the way, it is faster:

        System.Diagnostics.Stopwatch foo = new System.Diagnostics.Stopwatch();

        Decimal x = 1.5M;
        Decimal y = 1;
        int tests = 1000000;
        foo.Start();
        for (int z = 0; z < tests; ++z)
        {
            y = x - Decimal.Truncate(x);
        }
        foo.Stop();
        Console.WriteLine(foo.ElapsedMilliseconds);

        foo.Reset();
        foo.Start();
        for (int z = 0; z < tests; ++z)
        {
            y = x - Math.Floor(x);
        }
        foo.Stop();
        Console.WriteLine(foo.ElapsedMilliseconds);
        Console.ReadKey();

//Output: 123
//Output: 164

Edit: Fixed my explanation and code.

Brian
Yes, the querent said his issue was calling an external function. But I automatically ignored that in favor of what seemed to me to be the more obvious issue.
Brian
Doesn't Decimal.Truncate() return the integral digits? his solution returns the fractional portion. To get the result in wants, I think you need y = x - Decimal.Truncate(x);
Grant Wagner
@Grant: Yeah, I wasn't paying attention. It's still faster, though.
Brian
@After x - Decimal.Truncate(x) fix: I get ~125/100 timing in my tests using your code. But I think that ignores the real issue. x - Decimal.Truncate(x); gives you the CORRECT answer all the time. x - (float) Math.floor(x); gives you the WRONG answer at least some of the time (such as when x = 5.13f).
Grant Wagner
That is because Truncate works in base 10 whereas Floor works in base 2.
Brian