3

I'm creating two functions that are supposed to emulate and return the result of f(i) = 1/1 + 1/2 + 1/3 ... 1/i. One function is recursive, and I'm testing that the recursive function functions correctly by implementing a non-recursive version of it. However, I've found that both functions are returning similar answers that are not exactly the same. Can somebody please explain why the functions are returning different values?

When I run the functions in the main method of the class to which they belong, I get the following output:
Recursive for 1000: 7.4854784
Non-Recursive for 1000: 7.4854717
Recursive for 1: 1.0
Non-Recursive for 1: 1.0
Recursive for 483: 6.758268
Non-Recursive for 483: 6.758267

Here's my code:

static float RecursiveFunction(int num){
    //The num parameter represents the denominator that will be used
    //The recursive function is continually called at lower increments of num

    //If num is one, return 1 and do not call RecursiveFunction again
    if (num == 1) {
        return 1;
    }
    //Otherwise, return 1/num (in floating point decimal) and call RecursiveFunction with a parameter of num - 1
    else {
        return 1/(float)num + RecursiveFunction(num - 1);
    }
}

//A Non-recursive version of RecursiveFunction that will be used to test RecursiveFunction
static float NonRecursiveFunction(int num) {
    //The total variable adds up the fractions
    float total = 0;
    //While num is greater than zero, add 1/num to total and then subtract 1 from num
    while (num > 0) {
        total += 1/(float)num;
        num -= 1;
    }
    return total;
}
  • 4
    Looks like rounding issues due to using floating points... – assylias Feb 15 '14 at 00:36
  • You should use a debugger or logger to check every summand for rounding issues. – Smutje Feb 15 '14 at 00:45
  • 2
    It's because you're adding the floats in a different order. In the recursive version, you have 1, then you add 1/2, then 1/3, etc. In the non-recursive version, you have 1/483, then you add 1/482, then 1/481, etc. That's likely to lead to differences due to rounding. If you want the results to match, try using `for (i = 1; i <= num; i++)` in the non-recursive version. Off-hand, though, I think going backwards will give you a more accurate result, but I'm not sure. – ajb Feb 15 '14 at 00:47
  • Also, using `double` instead of `float` would give you more precision. – ajb Feb 15 '14 at 00:48

3 Answers3

3

It is due to rounding errors from using the float datatype which is a single-precision 32-bit IEEE 754 floating point. When the number requires a greater precision than the datatype can cope with it will be rounded - this will happen when you are adding multiple floats together.

If you convert your float datatypes to BigDecimal then you will get the same answer from both methods:

import java.math.BigDecimal;
import java.math.RoundingMode;

public class RoundingErrors {
    private static final BigDecimal ONE = new BigDecimal( 1 );

    static BigDecimal RecursiveFunction(int num){
        //The num parameter represents the denominator that will be used
        //The recursive function is continually called at lower increments of num

        //If num is one, return 1 and do not call RecursiveFunction again
        if (num == 1) {
            return ONE;
        }
        //Otherwise, return 1/num (in floating point decimal) and call RecursiveFunction with a parameter of num - 1
        else {
            return ONE.divide( new BigDecimal( num ), 100, RoundingMode.CEILING ).add( RecursiveFunction(num - 1) );
        }
    }

    //A Non-recursive version of RecursiveFunction that will be used to test RecursiveFunction
    static BigDecimal NonRecursiveFunction(int num) {
        //The total variable adds up the fractions
        BigDecimal total = new BigDecimal( 0 );
        //While num is greater than zero, add 1/num to total and then subtract 1 from num
        while (num > 0) {
            total = total.add( ONE.divide( new BigDecimal( num ), 100, RoundingMode.CEILING ) );
            num -= 1;
        }
        return total;
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println( RecursiveFunction( 1000 ));
        System.out.println( NonRecursiveFunction( 1000 ));
    }
}

Output

7.4854708605503449126565182043339001765216791697088036657736267499576993491652024409599344374118451321
7.4854708605503449126565182043339001765216791697088036657736267499576993491652024409599344374118451321
MT0
  • 143,790
  • 11
  • 59
  • 117
0

The only reason I can think of is because of the order of adding them together. The non-recursive function does this:

1/1 + (1/2 + (1/3 + (1/4 + (1/5 + (1/6 + 1/7)))))

While the recursive function does this:

((((((1/1 + 1/2) + 1/3) + 1/4) + 1/5) + 1/6) + 1/7)

This means that the recursive function is less precise. Why? Because floats are more dense around 0. So the closer to zero (of course, up to a certain level), the more precise your numbers are. The recursive function start with 1.5 and then starts adding smaller and smaller numbers. So you are immediately "pretty far away" from 0 in comparison to the flat function. The flat function sums the tiny numbers together first with high accuracy before getting to the bigger numbers.

I wrote a test program, and it demonstrates this explanation: http://ideone.com/4Eqduh. Output is:

Rec:             7.4854784
Flat0:           7.4854717
Flat1:           7.4854784
MT0 BigDecimal:  7.4854708605503449126

Comparing this with the result from MT0, you can indeed see that the Flat0 function (which sums the small numbers first) is the most accurate.

Martijn Courteaux
  • 67,591
  • 47
  • 198
  • 287
0

You may want to look at http://en.wikipedia.org/wiki/Harmonic_number, I'm not a mathematician so I only sort of understood it but it may provide a way for you to avoid recursion/looping all together.

Blue
  • 463
  • 4
  • 18