0

My assignment is as follows:

Read 20 pairs of numbers (ID number and score respectively) into two separate arrays. Find the average score. Print a table as shown below of the ID, score and difference (score - average) for each student, one student per line. Print the sum, average, and count of score at the head of the table as shown. Round the average and difference to 2 decimal places.

I have a bug in my program that should be really simple, but I just can't figure it out for some reason. I've figured out how to do the whole thing, but for some reason, my subtraction is off. Here's a sample output for reference:

ID  Score   Difference
115 257.0   14.349999999999994
123 253.0   10.349999999999994
116 246.0   3.3499999999999943
113 243.0   0.3499999999999943
112 239.0   -3.6500000000000057
104 239.0   -3.6500000000000057
110 238.0   -4.650000000000006
218 243.0   0.3499999999999943
208 242.0   -0.6500000000000057
222 223.0   -19.650000000000006
223 230.0   -12.650000000000006
213 229.0   -13.650000000000006
207 228.0   -14.650000000000006
203 224.0   -18.650000000000006
305 265.0   22.349999999999994
306 262.0   19.349999999999994
311 256.0   13.349999999999994
325 246.0   3.3499999999999943
321 245.0   2.3499999999999943
323 245.0   2.3499999999999943

The problem is that when I call my program to just print the average that it's computed, it's a nice rounded 242.65. And since the score[k] is obviously also a round number, I don't understand how this is happening? Any way you can shed some light on what's going wrong here?

import java.util.Scanner;
import java.io.*;

public class prog402a
{
    public static void main(String args[])
    {
        Scanner inFile = null;
        try
        {
            inFile = new Scanner(new File("prog402a.dat"));
        }
        catch (FileNotFoundException e)
        {
            System.out.println("File not found!");
            System.exit(0);
        }

        int[] idNumber = new int[20];
        double[] score = new double[20];

        for(int k = 0; k < idNumber.length; k++)
        {
            idNumber[k] = inFile.nextInt();
            score[k] = inFile.nextDouble();
        }

        double sum = 0;

        for(int k = 0; k < score.length; k++)
        {
            sum += score[k];
        }

        double doubSum = (double)sum;
        double average = doubSum/20.0;
        average=(int)(average*100+.5)/100.0;
        System.out.println(average);

        System.out.println("ID\tScore\tDifference");
        for(int k = 0; k < idNumber.length; k++)
        {
            System.out.println(idNumber[k] + "\t" + score[k] + "\t" + (score[k] - average));
        }



        inFile.close();

    }
}
Jens
  • 67,715
  • 15
  • 98
  • 113
nccows
  • 107
  • 1
  • 2
  • 10

3 Answers3

4

Floating point numbers (e.g. float and double numbers in Java) do not
have 100% precision in programming languages as they do in math.

So it may well be that a nice round number (in decimal) like e.g.

242.65

is represented internally (in the JVM e.g) as e.g. 242.64999999997.

I am just speaking roughly here but you get the idea.

Before printing each number just format it to the 2nd decimal digit and you should be fine. Let me try to code a small example for you... Here it is:

import java.text.DecimalFormat;
import java.text.NumberFormat;

public class Test020 {

    public static void main(String[] args) {

        double d = 13.5 + 0.1 + 0.11;

        System.out.println(d);

        NumberFormat formatter = new DecimalFormat("#0.00");     
        System.out.println(formatter.format(d));

    }

}
peter.petrov
  • 38,363
  • 16
  • 94
  • 159
  • Maybe this http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html ? – Ya Wang Feb 19 '15 at 16:19
  • @Invexity: for calculating average scores on 20 students, I don't think this is worth the effort... – Willem Van Onsem Feb 19 '15 at 16:19
  • @Invexity No, I think the OP just needs to round to the 2nd digit after the decimal point and the OP will be fine. – peter.petrov Feb 19 '15 at 16:20
  • Using doubles can also cause problems at the 2nd digit after the decimal point. Use BigDecimal, when you want to do calculations in the decimal system (like decimal rounding)! – slartidan Feb 19 '15 at 16:23
  • @slartidan: we're talking about 20 numbers, all within the same range (so no large difference in exponent). If so, I think it would still be more efficient to use *Kahan's algorithm*. – Willem Van Onsem Feb 19 '15 at 16:25
  • Yes, using BigDecimal here is OK but it's an overkill. OP just needs to format the numbers before outputting them. – peter.petrov Feb 19 '15 at 16:27
2

Floating points use the binary system to represent numbers. That means some numbers that can be represented with a finite amount of digits in the decimal system can't in the binary system. For instance there is no way to correctly represent 0.2 binary. As a result, the algorithm will use the closest value it can represent. Thus it will work with something 0.199999...-ish or 0.2000...1-ish. Note the decimal system has problems too, for instance representing 1/3 is done with 0.333333.... so if you would calculate with a limited number of digits. You will always make a (small) error.

There exists however a class - BigDecimal to that is able to calculate the score with an arbitrary precision, although I think calculating the average for 20 students will only differ on the 7th digit or so.

Therefore I recommend simply to format the numbers such that for instance, it displays the result precise on two digits. You can do this with:

DecimalFormat myFormatter = new DecimalFormat("0.000"); //the pattern
String output = myFormatter.format(number);
System.out.println(output);
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • This was the one I ended up using. Thank you! – nccows Feb 19 '15 at 16:27
  • +1 for good explanation of binary/decimal formats. Nevertheless I would use BigDecimal throughout my application in such cases. – slartidan Feb 19 '15 at 16:36
  • 1
    @slartidan: well it's an option and as you can see, it is mentioned in the answer. But you are not controlling a nuclear installation with this Java program, you are only calculating the average of the marks of students. I don't think students will care much whether the average is `15.12` or `15.11`. Furthermore it would be nice if Java would support *operator overloading* making it more convenient to work with these classes. Finally don't forget that `BigDecimal`s use algorithms working on arrays whereas `float`s can be calculated on the `80x87` coprocessor. – Willem Van Onsem Feb 19 '15 at 16:40
  • @CommuSoft I absolutly agree. And I'm also hoping to see operator overloading in the near future! – slartidan Feb 19 '15 at 16:41
0

As stated previously, the problem is that floats are not 100% accurate. The simplest solution in your case is probably String.format; change your print line to :

 System.out.println(
     String.format("%d\t%.2f\t%.2f", idNumber[k], score[k], score[k] - average));

This will output the numbers rounded to 2 decimal places.

Adrian Leonhard
  • 7,040
  • 2
  • 24
  • 38