递归函数和非递归函数返回不同的结果

时间:2014-02-15 00:34:20

标签: java recursion while-loop

我正在创建两个应该模拟并返回f(i)= 1/1 + 1/2 + 1/3 ... 1 / i结果的函数。一个函数是递归的,我正在测试递归函数通过实现它的非递归版本正确运行。但是,我发现这两个函数都返回了不完全相同的类似答案。有人可以解释为什么函数返回不同的值吗?

当我在它们所属的类的main方法中运行函数时,我得到以下输出:
递归1000:7.4854784
非递归1000:7.4854717
递归1:1.0
非递归1:1.0
递归483:6.758268
非递归483:6.758267

这是我的代码:

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;
}

3 个答案:

答案 0 :(得分:3)

这是由于使用float数据类型single-precision 32-bit IEEE 754 floating point来舍入错误。当数字需要比数据类型更高的精度时,它将被舍入 - 当你一起添加多个浮点数时会发生这种情况。

如果您将float数据类型转换为BigDecimal,那么您将从这两种方法中得到相同的答案:

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 ));
    }
}

<强>输出

7.4854708605503449126565182043339001765216791697088036657736267499576993491652024409599344374118451321
7.4854708605503449126565182043339001765216791697088036657736267499576993491652024409599344374118451321

答案 1 :(得分:0)

我能想到的唯一原因是因为将它们加在一起的顺序。非递归函数执行此操作:

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

虽然递归函数执行此操作:

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

这意味着递归函数不太精确。为什么?因为浮点数在0附近更密集。因此越接近于零(当然,达到一定水平),数字越精确。递归函数从1.5开始,然后开始添加越来越小的数字。所以你马上就会离这个很远的地方#34;与平面函数相比,从0开始。平坦函数在得到更大的数字之前,首先以高精度将微小数字加在一起。

我写了一个测试程序,它演示了这个解释:http://ideone.com/4Eqduh。 输出是:

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

将此与MT0的结果进行比较,您确实可以看到Flat0函数(首先对小数字求和)是最准确的。

答案 2 :(得分:0)

你可能想看看http://en.wikipedia.org/wiki/Harmonic_number,我不是数学家,所以我只是理解它,但它可以为你提供一种避免递归/循环的方法。