Double precision problems on .NET
Asked Answered
I

9

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.

Interconnect answered 19/2, 2009 at 20:15 Comment(0)
S
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.

Soleure answered 19/2, 2009 at 20:31 Comment(5)
but the uppercase F means double, not float, right? is the lowercase f that means float.Interconnect
No, i just checked : float x=1.0; gives an error, float x=1.0F; is OK. The F is not case-sensitive.Soleure
And looked it up in Ecmea334: 1.0D for double, 1.0M for decimal.Soleure
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...Waste
...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).Waste
B
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.

Buote answered 3/7, 2009 at 11:37 Comment(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.Hexarchy
P
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.

Pompei answered 19/2, 2009 at 21:0 Comment(3)
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.Mcilroy
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.Pompei
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.Underside
T
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.

Thebault answered 19/2, 2009 at 20:24 Comment(3)
It is an exact science within the IEEE defined number of significant digits.Pompei
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.Sciatica
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).Sciatica
S
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.

Soleure answered 19/2, 2009 at 20:31 Comment(5)
but the uppercase F means double, not float, right? is the lowercase f that means float.Interconnect
No, i just checked : float x=1.0; gives an error, float x=1.0F; is OK. The F is not case-sensitive.Soleure
And looked it up in Ecmea334: 1.0D for double, 1.0M for decimal.Soleure
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...Waste
...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).Waste
L
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.

Lancer answered 19/2, 2009 at 20:31 Comment(0)
R
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..."

Rycca answered 19/2, 2009 at 20:21 Comment(0)
H
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.

Hockett answered 19/2, 2009 at 20:25 Comment(0)
D
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));
}
Dani answered 22/4, 2009 at 8:25 Comment(0)
V
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

Vizza answered 12/1, 2010 at 1:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.