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 Prolog 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);
}
It worked now, but I could explain why?
– thg1
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
– Paulo R. F. Amorim
Another simple solution is
1d/i
.– Isac
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
– Jefferson Quesado