tags:

views:

292

answers:

8

I am making a Java program to calculate Simpson's rule for integrals. Here is the code I have. Notice the second column of numbers in the output values of count == 4,9,10,11. They are not numbers that I need, they do not follow the pattern. I need these numbers to be accurate. What is going on and how can I fix it?

public static void main(String[] args) 
{
   double totalS = 0.0;
   int count = 0;

   for(double i=0; i< 4; i += 0.4 )
   {
          count++;
          totalS += Sfunction(i, count);
          System.out.println(count + " " + i + " " + totalS);
   }
}

public static double Sfunction(double f1, int count)
{
    double value;

    if (f1 == 0.0 || f1 == 4.0)
        value = Math.cos(Math.sqrt(f1));
    else if ((count % 2) == 1)
        value = 2 * Math.cos(Math.sqrt(f1));
    else
        value = 4 * Math.cos(Math.sqrt(f1));

    return value;
}

I get the output of:

1    0.0    1.0 
2    0.4    4.226313639540303
3    0.8    5.478244888601832
4    1.2000000000000002    7.30884788480188
5    1.6    7.911122809972827
6    2.0    8.534897589034324
7    2.4    8.578100205110182
8    2.8    8.168723348285942
9    3.1999999999999997    7.736055200662704
10   3.5999999999999996    6.452869366954546
11   3.9999999999999996    5.620575693860261
+6  A: 

This is a classic floating point problem. If you need accuracy in your decimals, you should be using BigDecimal

Rob Di Marco
Doubles are likely to be sufficient for numerical integration.
duffymo
That won't completely solve the problem anyway. See http://stackoverflow.com/questions/2173512/java-bigdecimal-trigonometric-methods
finnw
+1  A: 

What you are seeing is a result of floating point precision error, the numbers aren't stored like you're probably thinking. You can round the answer to 1 decimal place to get rid of the error...but this is just a result of how doubles are stored in java.

Nick Craver
This is by the way not explicitly related to Java. Just to Floating Points in computing general and this is *regardless* of the programming language you're using.
BalusC
@BalusC - True, but the `.ToString()` in some languages accounts for this, so what you see in a console may differ.
Nick Craver
+4  A: 

This is how floating point numbers work in computers.

You can round the display, but the representation underneath won't change. Use java.text.DecimalNumberFormat to round to two decimal places.

duffymo
+1  A: 

There's some good reading on this topic over here: http://stackoverflow.com/questions/1594985/why-do-simple-math-operations-on-floating-point-return-unexpected-inacurate-res

Andrew
+1  A: 

Your problem is that you are using floating point arithmetic which can only approximate values, but assuming you have infinite precision. You shouldn't do equality tests like this with floating point numbers:

 if (f1 == 0.0 || f1 == 4.0)

Any equality test with a floating point number is a code smell. With a float you should always check if it lies within a certain range, for example in the range 3.9999 to 4.0001.

In this specific example though, you also handily have another parameter called count which is an int. You can do equality tests with that. Maybe you can test that instead.

Mark Byers
+1  A: 

try to print them with only one decimal digit:

System.out.printf("%.1f", Math.E); // prints 2.7
System.out.printf("%.2f", Math.E); // prints 2.72
System.out.printf("%.3f", Math.E); // prints 2.718

or even try to specify the keyword strictfp for your number crunching methods

dfa
+1  A: 

From your loop condition, it looks like you don't want line 11 to be processed at all. I recommend you use an integer loop index and use it to compute the values you pass to Sfunction. The following should be the equivalent of what you have now (except it leaves out line 11).

double totalS = 0.0;

for( int i = 1; i <= 10; i++ )
{
    double f1 = 0.4 * (i - 1);
    totalS += Sfunction(f1, i);
    System.out.println(i + " " + f1 + " " + totalS);
}

Your problem with print precision can be solved with DecimalFormat, as suggested in other answers.

Bill the Lizard
But it's simpler to use `Formatter` (via `System.out.printf()`) than `DecimalFormat`
finnw
@finnw: Of course you're right. I almost always forget they added printf to Java unless some form of intellisense reminds me.
Bill the Lizard
+6  A: 

Each time you go round your loop, you are compounding the error in the inexact addition of 0.4 to i.

Instead, use an integral value for the loop counter, and scale that to get a better approximation to the values:

    for ( int count = 0; count < 10; ++count ) {
        final double i = 0.4 * count;
        System.out.println ( ( count + 1 ) + " " + i );
    }

This will not eliminate the floating point error, but it will mean it is not increasing at each iteration. To remove the error from the output, format the output to a reasonable number of decimal places:

    for ( int count = 0; count < 10; ++count ) {
        final double i = 0.4 * count;
        System.out.printf ( "%2d %.1f\n", ( count + 1 ), i );
    }
Pete Kirkham
I took your idea and used DecimalFormat to fix the precision. Thanks
TrickyM66