17

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.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Jader Dias
  • 88,211
  • 155
  • 421
  • 625

9 Answers9

11

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
  • 11,369
  • 2
  • 37
  • 58
  • 1
    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 Nov 28 '09 at 05:27
8

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.

Community
  • 1
  • 1
Michael Meadows
  • 27,796
  • 4
  • 47
  • 63
  • 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 Feb 19 '09 at 22:12
  • 2
    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 Feb 19 '09 at 22:51
  • Michael, Your comment is silly. Defined this way, so that integers can be said to have "Infinite precison" just cause they donlt change during a math operation, then every number has "Infinite Precision", (floats, doubles and decimals don't change during math ops either). Defined this way, the concept loses all meaning. Heck, even just a sign with only two values, (Positive, or Negative) has "Infinite Precision", because, accccording to your definition, it loses no precision during any mathemeatical operation it is used in. – Charles Bretana May 23 '12 at 00:21
7

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
  • 8,940
  • 2
  • 34
  • 43
  • 5
    It is an exact science within the IEEE defined number of significant digits. – Michael Meadows Feb 19 '09 at 20:55
  • 1
    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 Jan 12 '10 at 01:55
  • 2
    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 Jan 12 '10 at 01:57
6

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 because of the F. 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, it's just not reliable.

H H
  • 263,252
  • 30
  • 330
  • 514
  • but the uppercase F means double, not float, right? is the lowercase f that means float. – Jader Dias Feb 19 '09 at 21:06
  • No, i just checked : float x=1.0; gives an error, float x=1.0F; is OK. The F is not case-sensitive. – H H Feb 19 '09 at 21:39
  • 1
    And looked it up in Ecmea334: 1.0D for double, 1.0M for decimal. – H H Feb 19 '09 at 21:45
  • It's unfortunate that Java's creators used backward logic in its widening/narrowing conversions rules for float and double backwards, and .net followed suit. Conversions from more specific types to less specific types should be considered widening if for every value in the source set there exists exactly one possible value in the destination (which may be shared by other values in the source set). The value `0.1f` doesn't mean "13421773/134217728". It means "something between 13421772.5/134217728 and 13421773.5/134217728". Many `double` values could fit that description; arbitrarily... – supercat May 29 '12 at 22:06
  • ...selecting the exact value which is in the middle of the range may be a convenient thing for a compiler to do, but it should be considered sufficiently dangerous that compilers shouldn't do it without a warning. By contrast, converting double to single is safe; after conversion, the value is be less specific than before the conversion, but the value will be accurate to its claimed precision (which would not be the case after a single-to-double conversion). – supercat May 29 '12 at 22:10
5

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.

Amy B
  • 108,202
  • 21
  • 135
  • 185
3

Check the answers to this question: Is it safe to check floating point values for equality to 0?

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

Community
  • 1
  • 1
hometoast
  • 11,522
  • 5
  • 41
  • 58
1

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.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
0

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
  • 3,427
  • 1
  • 30
  • 35
0

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
  • 249
  • 2
  • 3
  • 10