2

Strange output when I use float instead of double

#include <stdio.h>
void main()
{
    double p,p1,cost,cost1=30;
    for (p = 0.1; p < 10;p=p+0.1)
    {
        cost = 30-6*p+p*p;
        if (cost<cost1)
        {
            cost1=cost;
            p1=p;
        }
        else
        {
            break;
        }
        printf("%lf\t%lf\n",p,cost);
    }
    printf("%lf\t%lf\n",p1,cost1);
}

As Expected

Gives output as expected at p = 3;

But when I use float the output is a little weird.

#include <stdio.h>
void main()
{
    float p,p1,cost,cost1=40;
    for (p = 0.1; p < 10;p=p+0.1)
    {
        cost = 30-6*p+p*p;
        if (cost<cost1)
        {
            cost1=cost;
            p1=p;
        }
        else
        {
            break;
        }
        printf("%f\t%f\n",p,cost);
    }
    printf("%f\t%f\n",p1,cost1);
}

Strange increment style

Why is the increment of p in the second case going weird after 2.7?

yellowantphil
  • 1,483
  • 5
  • 21
  • 30
Vivek Aditya
  • 1,145
  • 17
  • 46
  • 2
    Looks like a rounding error to me since float has fewer significant digits than a double. – Richard Chambers Feb 14 '15 at 05:12
  • 1
    I am just adding 0.1 to p . What is there for rounding in it ? Even if it is so why after 2.7 not before. – Vivek Aditya Feb 14 '15 at 05:14
  • 4
    floating point types are the cats of numerical variables. No matter how you herd them they still do what they please. – Richard Chambers Feb 14 '15 at 05:17
  • @ysaditya: it's in the binary representation. 0.1 doesn't work well in binary floating point. – Fred Larson Feb 14 '15 at 05:17
  • 2
    The double is probably `2.799999999999` - with more 9's, so it still gets rounded to 2.8. – user253751 Feb 14 '15 at 05:18
  • 1
    @S.Morgenstern Morgenstern Are you sure that it will happen with `double` as well . I tried larger numbers but the behavior is still the same. – Vivek Aditya Feb 14 '15 at 05:21
  • 3
    @ysaditya: http://floating-point-gui.de/ ... – akira Feb 14 '15 at 06:42
  • 1
    possible duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – tmyklebu Feb 14 '15 at 09:28
  • I've voted to close this as a duplicate of "is floating point math broken" as it better addresses the fundamental issue at play here. I would add that you are printing your numbers to only six decimal places and are not seeing the roundoff in the `double` case. – tmyklebu Feb 14 '15 at 09:29
  • 1
    @tmyklebu Since we are discussing each other's close votes, the reason I would be reluctant to close as a duplicate of that question is that the accepted answer is the boring “don't use `==` between floats. Always compare up to an arbitrary epsilon that I pulled out of my hat to make the example in your question work but do not tell you how to choose, or know myself”. This question is not about `==`. – Pascal Cuoq Feb 14 '15 at 14:03
  • @PascalCuoq: Oh barf. Still? What's the right dupe called? – tmyklebu Feb 14 '15 at 17:44
  • @tmyklebu Looked for it, didn't find it, wrote an answer :) – Pascal Cuoq Feb 14 '15 at 17:45
  • @PascalCuoq: I have taken advantage of the accepted answer's community wiki status and edited it. (I hope this doesn't run afoul of Stack Overflow's guidelines.) – tmyklebu Feb 14 '15 at 18:07
  • @tmyklebu That edit is a clear improvement and completely in line for a community wiki answer. I will use this question again as duplicate target. – Pascal Cuoq Feb 14 '15 at 19:11

2 Answers2

12

This is happening because the float and double data types store numbers in base 2. Most base-10 numbers can’t be stored exactly. Rounding errors add up much more quickly when using floats. Outside of embedded applications with limited memory, it’s generally better, or at least easier, to use doubles for this reason.

To see this happening for double types, consider the output of this code:

#include <stdio.h>
int main(void)
{
    double d = 0.0;
    for (int i = 0; i < 100000000; i++)
        d += 0.1;
    printf("%f\n", d);
    return 0;
}

On my computer, it outputs 9999999.981129. So after 100 million iterations, rounding error made a difference of 0.018871 in the result.

For more information about how floating-point data types work, read What Every Computer Scientist Should Know About Floating-Point Arithmetic. Or, as akira mentioned in a comment, see the Floating-Point Guide.

yellowantphil
  • 1,483
  • 5
  • 21
  • 30
6

Your program can work fine with float. You don't need double to compute a table of 100 values to a few significant digits. You can use double, and if you do, it will have chances to work even if you use binary floating-point binary at cross-purposes. The IEEE 754 double-precision format used for double by most C compilers is so precise that it makes many misuses of floating-point unnoticeable (but not all of them).

Values that are simple in decimal may not be simple in binary

A consequence is that a value that is simple in decimal may not be represented exactly in binary.

This is the case for 0.1: it is not simple in binary, and it is not represented exactly as either double or float, but the double representation has more digits and as a result, is closer to the intended value 1/10.

Floating-point operations are not exact in general

Binary floating-point operations in a format such as float or double have to produce a result in the intended format. This leads to some digits having to be dropped from the result each time an operation is computed. When using binary floating-point in an advanced manner, the programmer sometimes knows that the result will have few enough digits for all the digits to be represented in the format (in other words, sometimes a floating-point operation can be exact and advanced programmers can predict and take advantage of conditions in which this happens). But here, you are adding 0.1, which is not simple and (in binary) uses all the available digits, so most of the times, this addition is not be exact.

How to print a small table of values using only float

In for (p = 0.1; p < 10;p=p+0.1), the value of p, being a float, will be rounded at each iteration. Each iteration will be computed from a previous iteration that was already rounded, so the rounding errors will accumulate and make the end result drift away from the intended, mathematical value.

Here is a list of improvements over what you wrote, in reverse order of exactness:

for (i = 1, p = 0.1f; i < 100; i++, p = i * 0.1f)

In the above version, 0.1f is not exactly 1/10, but the computation of p involves only one multiplication and one rounding, instead of up to 100. That version gives a more precise approximation of i/10.

for (i = 1, p = 0.1f; i < 100; i++, p = i * 0.1)

In the very slightly different version above, i is multiplied by the double value 0.1, which more closely approximates 1/10. The result is always the closest float to i/10, but this solution is cheating a bit, since it uses a double multiplication. I said a solution existed with only float!

for (i = 1, p = 0.1f; i < 100; i++, p = i / 10.0f)

In this last solution, p is computed as the division of i, represented exactly as a float because it is a small integer, by 10.0f, which is also exact for the same reason. The only computation approximation is that of a single operation, and the arguments are exactly what we wanted them to, so this is the best solution. It produces the closest float to i/10 for all values of i between 1 and 99.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281