a < b 与 a - b < 0

在查看集合源码时, 发现许多地方都用到了 a - b < 0, 并注释了 overfolw-conscious code 如:

// overflow-conscious code
   if (minCapacity - elementData.length > 0)
       grow(minCapacity);

于是想探讨一下它们的区别。

分析

如果 a, b 都没有发生溢出的情况下 \(a - b < 0\)\(a < b\) 显然是一样的, 所以要当它俩至少有一个溢出时这个问题才有意义, 以下分三种情况讨论 (为简化情况, 假设 a, b 都大于 0 )。

ps: 在计算机中的正数溢出之后, 一般变为一个绝对值较大的负数。

a 溢出 b 不溢出

a 溢出, 导致 a 变为负数; b 没溢出, 所以还是正数。

事实: 此时是 \(b < Integer.MAX\_VALUE < a\)

  • 使用 a < b 时: 这个表达式为 true, 但与事实 \(b < a\) 不符。

  • 使用 a - b < 0 时:

    • b 很小时: \(a - b\) 不再溢出, 但 \(a - b < 0\), 与事实 \(b < a\) 不符。
    • b 很大时, \(a - b\) 再次溢出, 导致 \(a - b > 0\), 符合预期。

a 不溢出 b 溢出

b 溢出, 导致 b 变为负数; a 没溢出, 所以还是整数。

事实: 此时是 \(a < Integer.MAX\_VALUE < b\)

  • 使用 a < b 时: 这个表达式为 \(false\), 但与事实 \(a < b\) 不符。

  • 使用 a - b < 0 时:

    • a 很小时: \(a - b\) 不再溢出, 但 \(a - b > 0\), 与事实 \(a < b\) 不符。
    • a 很大时: \(a - b\) 再次溢出, 导致 \(a - b < 0\) , 符合预期。

a, b都溢出

无法判断


以上的分析中用了"很小", "很大"的字眼, 其实是有限定的, 例如:

int a = Integer.MAX_VALUE;
a += 20;  // a 若溢出 20, 则 20 为临界点

int b = 15;  // 此时 b "很小"
int c = 25;  // 此时 c "很大"

结论

综合上面的分析,两种写法都不是万能的,在某些情况下还是与预期不符,但 JDK之所以采用 \(a - b < 0\) 的写法是因为: 在确保 a 和 b 数值接近的情况下,其中一个先溢出,另一个还没溢出但是也很大了,此时 \(a - b < 0\) 的写法刚好得到正确结果。

JDK 采用这种写法,一般是在各种空间的分配中,例如 ensureCapacity()。而分配空间时,新空间大小虽然比旧空间大,但它俩往往是差不多的(也即刚说的 "数值接近"),使用这种写法可以巧妙的化解溢出的问题。

posted @ 2023-04-21 21:35    阅读(30)  评论(0)    收藏  举报