tags:

views:

9649

answers:

9

I have a simple C# function:

    public static double Floor(double value, double step)
    {
        return Math.Floor(value / step) * step;
    }

That calculates the higher number, lower than or equal to "value", that is multiple of "step". But it lacks precision, as seen in the following tests:

    [TestMethod()]
    public void FloorTest()
    {
        int decimals = 6;
        double value = 5F;
        double step = 2F;
        double expected = 4F;
        double actual = Class.Floor(value, step);
        Assert.AreEqual(expected, actual);
        value = -11.5F;
        step = 1.1F;
        expected = -12.1F;
        actual = Class.Floor(value, step);
        Assert.AreEqual(Math.Round(expected, decimals), Math.Round(actual, decimals));
        Assert.AreEqual(expected, actual);
    }

The first and second asserts are ok, but the third fails, because the result is only equal until the 6th decimal place. Why is that? Is there any way to correct this?

Update If I debug the test I see that the values are equal until the 8th decimal place instead of the 6th, maybe because Math.Round introduces some imprecision.

Note In my test code I wrote the "F" suffix (explicit float constant) where I meant "D" (double), so if I change that I can have more precision.

+3  A: 

Check the answers to this question: http://stackoverflow.com/questions/485175/c-net-is-it-safe-to-check-floating-point-values-for-equality-to-0

Really, just check for "within tolerance of..."

hometoast
+5  A: 

Floating point arithmetic on computers are not Exact Science :).

If you want exact precision to a predefined number of decimals use Decimal instead of double or accept a minor interval.

veggerby
It is an exact science within the IEEE defined number of significant digits.
Michael Meadows
To reinforce the above: floating point numbers _are_ exact. The number you want might not be able to be represented as an IEEE floating point number, which means you must alias the number to the next closest one, which leads to error, but that doesn't mean the numbers you _can_ represent have error in them.
codekaizen
Also, Decimals can suffer from the same problem as Doubles, since they, too, are floating point. They can encounter the same representation problem, but it's much less unexpected, since they have a base of 10, vs. a base of 2, and we are used to dealing with representation issues in base 10 (e.g. 1/3 is 0.333333... in base 10).
codekaizen
+1  A: 

floats and doubles cannot accurately store all numbers. This is a limitation with the IEEE floating point system. In order to have faithful precision you need to use a more advanced math library.

If you don't need precision past a certain point, then perhaps decimal will work better for you. It has a higher precision than double.

Mystere Man
+3  A: 

If you omit all the F postfixes (ie -12.1 instead of -12.1F) you will get equality to a few digits more. Your constants (and especially the expected values) are now floats. If you are doing that on purpose then please explain.

But for the rest i concur with the other answers on comparing double or float values for equality.

Henk Holterman
but the uppercase F means double, not float, right? is the lowercase f that means float.
Jader Dias
No, i just checked : float x=1.0; gives an error, float x=1.0F; is OK. The F is not case-sensitive.
Henk Holterman
And looked it up in Ecmea334: 1.0D for double, 1.0M for decimal.
Henk Holterman
Wow. I was certain of it until now, many many many thanks!
Jader Dias
+3  A: 

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

For example, the non-representability of 0.1 and 0.01 (in binary) means that the result of attempting to square 0.1 is neither 0.01 nor the representable number closest to it.

Only use floating point if you want a machine's interpretation (binary) of number systems. You can't represent 10 cents.

David B
+2  A: 

If you want precision, use System.Decimal. If you want speed, use System.Double (or System.Float). Floating point numbers are not "infinite precision" numbers, and therefore asserting equality must include a tolerance. As long as your numbers have a reasonable number of significant digits, this is ok.

  • If you're looking to do math on very large AND very small numbers, don't use float or double.
  • If you need infinite precision, don't use float or double.
  • If you are aggregating a very large number of values, don't use float or double (the errors will compound themselves).
  • If you need speed and size, use float or double.

See this answer (also by me) for a detailed analysis of how precision affects the outcome of your mathematical operations.

Michael Meadows
There is no 'infinite precision'. The problem with float/double is that they are precise to a number of binary digits and not to a number of decimal digits.
configurator
There is such a thing as infinite precision. An integer is an infinitely precise type. It looses no precision during mathematical operations. It is possible to implement an infinitely precise (although very inefficient) decimal type, but it isn't "out of the box" in .Net.
Michael Meadows
A: 

For the similar issue, I end up using the following implementation which seems to success most of my test case (up to 5 digit precision):

public static double roundValue(double rawValue, double valueTick)
{
    if (valueTick <= 0.0) return 0.0;

    Decimal val = new Decimal(rawValue);
    Decimal step = new Decimal(valueTick);
    Decimal modulo = Decimal.Round(Decimal.Divide(val,step));

    return Decimal.ToDouble(Decimal.Multiply(modulo, step));
}
Seb
+3  A: 

I actually sort of wish they hadn't implemented the == operator for floats and doubles. It's almost always the wrong thing to do to ever ask if a double or a float is equal to any other value.

Jon Grant
YES! YES! YES! I've been saying that for a while now. It's like the whole 0.999... = 1.0 problem. (1.0 - 0.000... = 1.0). Floating points are a whole different animal than integers.
Josh Einstein
A: 

Sometimes the result is more precise than you would expect from strict:FP IEEE 754. That's because HW uses more bits for the computation. See C# specification and this article

Java has strictfp keyword and C++ have compiler switches. I miss that option in .NET

Pavel Savara