If H = 1 + ½ +1/3 + ¼ + ... + 1/N, make an algorithm to calculate H, where N is typed by the user

Asked

Viewed 4,373 times

5

The program is only printing 1, it’s like it doesn’t keep the value of the variable h, can anyone help? Follow the code:

public class MainUmSobreH {
public static void main(String[] args) {
    double h = 0;
    int n = 4;
    for(int i=1; i<=n; i++) {

        h = h + 1/i;

        System.out.println(h);
    }
}
}

3 answers

5

Floating point numbers are mythological beasts of wandering behavior. Or at least they are so if you do not know how to tame them before using them.

Using floating points, I can add up 3 numbers in one order and get a distinct result than adding up those same numbers in another order:

a + b + c =?= c + a + b

I briefly discuss the subject in a few responses:

  • in this one, I have to make a summation f(n)*b for various values of n; one of the strategies I did not put the b in evidence, so the sum was sum = f(1)*b + f(2)*b + f(3)*b, with 3 multiplications and 2 sums, in the other alternative I only multiply at the end of the sum sum = b*(f(1) + f(2) + f(3)) with 2 sums and 1 multiplication; and, no, they are not equivalent sums when taking into account the precision of the floating point
  • in that other answer, I used this fact of loss of precision as a stop condition in an infinite sum, including I show that there are cases in which add up a + y == a, y != 0, therefore (a + y) + y == a, but there may be a + (y + y) > a

So what would be the way to try to ensure as much accuracy as possible in the sum? Adding from the least significant value to the most significant! Basically change the order of for proposed by @Paulo R. F. Amorim. It is also possible to do this in recursion proposed by @Marcos Andrade with a little more caution.

Iterated solution

double sum = 0d;
int n = 4
for (int i = n; i >= 1; i--) {
    sum += 1d/i;
}

I’m using the notation indicating that the number is a double in accordance with proposed by @Isac. Another alternative would be doing 1.0/i, which forces the number to be interpreted as double anyway.

Recursive solution 1

The idea here is to have an interface function that calls the recursive function itself. Let’s pass, in the recursive function, the sum accumulated until then, so we can keep adding the smallest elements first to then add the most significant element.

This strategy resembles a few solutions used in when it is not desired that those who go to the database need to know that the list to be passed as a third non-intuitive argument needs to be an empty list.

public static double somaInverso(int n) {
    return somaInversoPrivado(n, 0.0);
}

private static double somaInversoPrivado(int n, double acumulado) {
  if (n == 1) {
    return 1.0 + acumulado;
  }
  return somaInversoPrivado(n-1, 1.0/n + acumulado);
}

Note that here I left the method somaInversoPrivado as a private method to the class that makes these calculations. It is an auxiliary function whose API shall not be exposed.

Recursive solution 2

Here things happen in a similar way to the other, but instead of sending the accumulated sum, I send in what step in recursion I am. I have to stop when I’m in step n. The idea is that the result of the step i is 1.0/i + sum(i+1, n), this ensures that in the last recursive step the lowest value is returned, which will then be summed with the second lowest value and so on.

public static double somaInverso(int n) {
    return somaInversoPrivado(1, n);
}

private static double somaInversoPrivado(int i, int n) {
  if (n == i) {
    return 1.0/i;
  }
  return 1.0/i + somaInversoPrivado(i + 1, n);
}

5


Necessary to convert into double when adding/dividing:

public class MainUmSobreH {
    public static void main(String[] args) {
      double h = 0;
      int n = 4;
      for(int i=1; i<=n; i++) {

        h = h + (double)1/i;

        System.out.println(h);
    }
}

So it should work properly.

  • 1

    It worked now, but I could explain why?

  • 2

    Because as it was being done before, both numbers (both fixed 1 and "i" for) were defined as integers, so the division always gave an integer round. For example, 1/2 was not 0.5, but 0

  • 2

    Another simple solution is 1d/i.

  • In extreme cases, the answer may end up generating a possible accuracy error. I do not know if this error will be sensitive with the floating point format of double precision, but there is the possibility of this happening

4

This algorithm is also a classic example of recursion. I find it interesting to leave here as an alternative way to end the example.

Simplifying the algorithm (without checking for exceptions etc):

package recursividade;

public class Progressao {

    public static void main(String[] args) {
        double inicial = 0;
        int limite = 4;
        double resultado = inicial + CalcularProgressao(limite);
        System.out.println(resultado);
    }

    public static double CalcularProgressao(int limite) {
        if (limite == 1) {
            return 1.0;
        }
        else {
            return 1.0/limite + CalcularProgressao(limite-1);
        }
    }
}

It is important to mention that Return uses 1.0 instead of 1, since it is necessary to return a Double.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.