现在我们的int里程表是这样的:

  1. 范围是 -50004999(模拟int从负数到正数)。
  2. 超过最大值 4999 会怎么样? 再走1公里,它会溢出,变成-5000
  3. 低于最小值 -5000 会怎么样? 再倒车1公里,它会下溢,变成4999

场景:
你是个超级司机,已经开了 4990 公里 (newCapacity = 4990),这已经快爆表了。
老板要求你至少开 4995 公里 (minCapacity = 4995)。

你接着开了10公里去完成任务。
你的里程表从 4990 开始增加:
4991 -> 4992 -> 4993 -> 4994 -> 4995 -> 4996 -> 4997 -> 4998 -> 4999 -> -5000
最终,你的里程表显示:-5000

现在我们来判断任务是否完成。


方法一:直接比较(错误方法)

  • 你的里程表(newCapacity)-5000
  • 老板的要求(minCapacity)4995

你用直接比较法-5000 < 4995
是的,-5000 显然小于 4995
结论:里程不够!

但这完全错了!你明明已经开了整整5000公里(从4990到-5000),远远超过了老板要求的4995公里!只是里程表溢出成了负数。这个方法在极端情况下失效了。


方法二:减法比较(Java源码的方法)

你的里程表显示:-5000
老板的要求:4995

Java源码的方法:“请你用你的里程,减去老板的要求,看看结果是正还是负?
计算:-5000 - 4995

这个计算本身也会溢出! 我们来算一下:

  1. -5000 - 4995 数学上等于 -9995
  2. 但我们的里程表只能显示 -50004999-9995 远远小于最小值-5000
  3. 所以结果会下溢,绕一大圈变成正数。

    • -5000 - 1 = -5001 (低于-5000,下溢) -> 4999
    • -5000 - 2 = -5002 -> 4998
    • ...
    • -5000 - 4995 = -9995 会下溢成 -9995 - (-5000) + 4999 + 1 的复杂计算,最终结果是一个小的正数,比如 5

最终,(-5000 - 4995) 在这个里程表世界里,结果是一个正数,比如 5

现在,Java源码的条件是:(你的里程 - 老板要求) < 0
我们算出来的结果是 5(一个正数)。
5 < 0 吗?

结论:里程足够!

这个结论是正确的


为什么“溢出后的减法”能得出正确结论?

核心奥秘在于:CPU进行加减法时,并不关心数字的“数学值”,它只关心比特位(0和1)的运算。

  1. 符号位是裁判:CPU做完减法后,会看一下结果的最高位(符号位)。如果是0,就是正数或零;如果是1,就是负数。
  2. 溢出不影响裁判:即使加减过程中发生了溢出,导致比特位全部错乱,这个“符号位”的判断规则也永远不会错。它依然能忠实地告诉你,在计算机的世界里,这次减法的结果是正还是负。
  3. 逻辑关系保持不变:最关键的是,a - b 的运算结果的符号位,和 a < b 这个逻辑关系的真假,在CPU的运算规则下是完全一致的。即使发生溢出,这个对应关系也雷打不动。

    • 如果 a < b,那么 a - b 在CPU看来结果就是负数(符号位为1)。
    • 如果 a >= b,那么 a - b 在CPU看来结果就是正数或零(符号位为0)。

所以,if (a - b < 0) 这个条件,本质上是在问CPU:“刚才的减法,结果符号位是1吗?”

而直接比较 if (a < b),在溢出时,比较的双方 ab 本身就已经是错误的值了,所以会得出错误结论。

Java源码相信的是CPU硬件级别的、永不出错的符号位判断,而不是已经溢出失真的数值本身。这就是这个技巧如此强大和可靠的原因。

分类: Java-Backend 标签: Java

评论

暂无评论数据

暂无评论数据

目录