当一个for循环应该递增时,是什么原因导致其递减?

5
我写了一个方法来计算父亲是儿子两倍年龄时的时间,以及从现在开始多少年后这个条件仍然成立。但是,当父亲8岁,儿子3岁时,它返回“2年前”,而当父亲3岁,儿子2岁时,它返回“1年后”。我不关心如何改进代码,因为我已经知道如何做到这一点。相反,我困惑的是为什么for循环计数器在应该递增时出现递减的情况。
以下是我的代码。
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);
    }
}

通过twiceAsOld(8, 3)函数的调用,for循环的增量似乎已经反转,从0开始递减而不是递增。在twiceAsOld(3, 2)函数中,-1可能表示一个错误,表明父亲从未是儿子的两倍年龄,也永远不会是。我不明白的是,什么会导致for循环在应该递增时开始递减i值。我原本期望计数器将无限递增,直到程序耗尽内存。
我已经知道如何改进这个程序,但我想知道在for循环中计数器如何在应该递增时递减。有人能解释一下吗?
(更新:感谢大家的回答。我简直不敢相信我忘了整数溢出。我试着把变量从int改成long,但这使程序变得更慢了。不管怎样,现在我意识到计数器一直在递增,直到溢出并降至负值。)

1
由于该值没有减少,几乎可以确定这是整数溢出。 - Marc Baumbach
3个回答

4
它变成负数是因为在Java中,当int计算溢出时会发生这种情况。请查看https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.18.2。它说:如果整数加法溢出,则结果是某个足够大的二进制补码格式中表示的数学和的低位比特。如果发生溢出,则结果的符号与两个操作数值的数学和的符号不同。

3

你有没有注意到你的程序运行得相当慢? :)

对于(8,3)年前的情况,你的for循环一直在循环,试图找到一个使父亲年龄加倍的年份,但我们知道,父亲只会在未来成为两倍老,而不是在过去。for循环不知道这一点,它会非常努力地寻找这样的一年。它尝试了非常努力,以至于yearsAgo增加到超过int的最大值。这导致溢出yearsAgo的值将“回绕”到int的最小值,即负数。然后这个负数会被增加很多很多次,直到-2。

其他情况也是如此。

要解决这个问题,你可以添加if语句来检查结果是否为负数:

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

}

你可以在循环达到负值时停止它,以使程序运行更快:
for (yearsAgo = 0; pastFathersAge != 2 * pastSonsAge && yearsAgo >= 0; yearsAgo++) {

1
当我调试你的代码时,我发现yearsAgo在不断增加,导致pastFathersAgepastSonsAge变成负数。这会导致负整数溢出。这是因为你的条件pastFathersAge != 2 * pastSonsAge从未被满足(而是从未被不满足)。直到你的futureFathersAge一直经过负数,回到正数,并最终定居在-2。
故事的寓意是要确保您循环的终止条件始终能够满足。不要使用!=,而应该使用>=<=

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接