What would cause a for loop to decrement when it's supposed to increment?
Asked Answered
M

3

5

I wrote a method to calculate how long ago a father was twice as old as his son and in how many years from now this would be true. Unexpectedly, it returns "-2 years ago" for an 8-year-old father and a 3-year-old son. Equally unexpectedly, it returns "-1 years from now" for a 3-year-old father and a 2-year-old son. I am not concerned about how to improve the code because I already know how to do this. Instead, I am puzzled about why the for loop counter appears to be decrementing when it's supposed to increment.

Here is my code.

public class TwiceAsOld {

    public static void twiceAsOld (int currentFathersAge, int currentSonsAge) {

        int yearsAgo;
        int yearsFromNow;
        int pastFathersAge = currentFathersAge;
        int pastSonsAge = currentSonsAge;
        int futureFathersAge = currentFathersAge;
        int futureSonsAge = currentSonsAge;

        for (yearsAgo = 0; pastFathersAge != 2 * pastSonsAge; yearsAgo++) {
            pastFathersAge--;
            pastSonsAge--;
        }

        System.out.println("The father was last twice as old as the son " + yearsAgo + " years ago.");

        for (yearsFromNow = 0; futureFathersAge != 2 * futureSonsAge; yearsFromNow++) {
            futureFathersAge++;
            futureSonsAge++;
        }

        System.out.println("The father will be twice as old as the son in " + yearsFromNow + " years from now.");

    }

    public static void main(String[] args) {
        twiceAsOld(8, 3);
        twiceAsOld(3, 2);
    }
}

With twiceAsOld(8, 3), the for loop's increment appears to have reversed itself to count down from 0 instead of up. With twiceAsOld(3, 2), the -1 might stand for an error indicating that the father has never been twice as old as his son and never will be. What I don't understand is what would cause a for loop to start decrementing the i value when it's supposed to increment. I was expecting the counter to increment indefinitely until the program ran out of memory.

I already know how to improve this program, but I am curious about how the counter in a for loop can decrease when it's supposed to increase. Can anybody explain this?

(UPDATE: Thanks everyone for your answers. I can't believe I forgot about integer overflow. I tried making the variables longs instead of integers, but this made the program even slower. Anyway, now I realize that the counter was incrementing all along until it overflew and landed at a negative value.)

Malorie answered 3/1, 2019 at 1:50 Comment(1)
Since there's no decrement of that value, it is almost certainly an integer overflow.Pursuivant
R
4

It became negative because that is what happens in Java when an int calculation overflows.

Take a look at https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.18.2

It says that

If an integer addition overflows, then the result is the low-order bits of the mathematical sum as represented in some sufficiently large two's-complement format. If overflow occurs, then the sign of the result is not the same as the sign of the mathematical sum of the two operand values.

Riba answered 3/1, 2019 at 2:1 Comment(0)
M
3

Didn't you notice that your program runs quite slowly? :)

For the (8, 3) years ago case, your for loop keeps looping and looping, trying to find a year that the father is twice as old, but as we know, the father will only become twice as old in the future, but not in the past. The for loop doesn't know this and it will try very hard to find such a year. It tries so hard that yearsAgo is incremented past the max value of int. This causes an overflow, and the value of yearsAgo will "wrap back around" to the minimum value of int, which is a negative number. And then this negative number will get incremented many many times, until -2.

The same goes for the other case.

To fix this, you can add if statements to check if the results are negative:

public static void twiceAsOld (int currentFathersAge, int currentSonsAge) {

    int yearsAgo;
    int yearsFromNow;
    int pastFathersAge = currentFathersAge;
    int pastSonsAge = currentSonsAge;
    int futureFathersAge = currentFathersAge;
    int futureSonsAge = currentSonsAge;


    for (yearsAgo = 0; pastFathersAge != 2 * pastSonsAge; yearsAgo++) {

        pastFathersAge--;
        pastSonsAge--;
    }

    // Here!
    if (yearsAgo >= 0) {
        System.out.println("The father was last twice as old as the son " + yearsAgo + " years ago.");
    }

    for (yearsFromNow = 0; futureFathersAge != 2 * futureSonsAge; yearsFromNow++) {
        futureFathersAge++;
        futureSonsAge++;
    }

    if (yearsFromNow >= 0) {
        System.out.println("The father will be twice as old as the son in " + yearsFromNow + " years from now.");
    }

}

You can also stop the loop when it reaches negative values to make your program faster:

for (yearsAgo = 0; pastFathersAge != 2 * pastSonsAge && yearsAgo >= 0; yearsAgo++) {
Materiel answered 3/1, 2019 at 2:6 Comment(0)
C
1

When I debug your code I can see that yearsAgo is incrementing without bound, causing pastFathersAge and pastSonsAge to go into negatives. This is causing negative integer overflow. This happens because your condition pastFathersAge != 2 * pastSonsAge is never met (rather, never NOT met). Not until your futureFathersAge has gone all the way through the negatives, back into positives, and finally settles on -2.

The moral of the story is to make certain that your terminating condition for your loop can always can be met. Don't use !=, use >= or <= instead.

Cinchonism answered 3/1, 2019 at 2:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.