我正在尝试编写一个代码来确定自 1970 年初以来的毫秒数何时会超过长整型的容量。以下代码似乎可以完成这项工作:
public class Y2K {
public static void main(String[] args) {
int year = 1970;
long cumSeconds = 0;
while (cumSeconds < Long.MAX_VALUE) {
// 31557600000 is the number of milliseconds in a year
cumSeconds += 3.15576E+10;
year++;
}
System.out.println(year);
}
}
此代码在几秒钟内执行并打印292272992。如果我不使用科学记数法,我将cumSeconds写为31558000000L
,该程序似乎需要"永远"才能运行(我只是在10分钟左右后暂停(。另请注意,用科学记数法编写 cumSeconds 不需要指定数字是末尾带有 L 或 l 的long
。
它之所以有所不同,是因为科学记数法3.1558E+10
是double
文字,而文字31558000000L
当然是long
文字。
这使得+=
运算符大不相同。
形式为 E1 op= E2 的复合赋值表达式等效于 E1 = (T( ((E1(基本上,多头 += 多头产生多头op (E2((,其中 T 是 E1 的类型,只是 E1 只计算一次。
,但多头 += 双倍也产生多头。
当添加double
时,cumSeconds
的初始值被扩大到double
然后进行加法。 结果经历了缩小的原始转换回long
。
将浮点数缩小到整数类型 T 的转换需要两个步骤:
- 在第一步中,如果 T 长,则将浮点数转换为长整型
(截图(
否则,必须满足以下两种情况之一:
该值必须太小(大量级或负无穷大的负值(,第一步的结果是 int 或 long 类型的最小可表示值。
该值必须太大(大量级或正无穷大的正值(,并且第一步的结果是 int 或 long 类型的最大可表示值。
(粗体强调我的(
结果最终太大而无法用long
表示,所以结果缩小到Long.MAX_VALUE
,while
循环结束。
但是,当您使用long
文本时,您不断将偶数值添加到偶数值,该值最终将溢出。 这不会将值设置为 Long.MAX_VALUE
,这是奇数,因此循环是无限的。
但是,与其依赖最终产生Long.MAX_VALUE
的加法,不如使用Java 1.8+显式测试Math.addExact
溢出。
返回其参数的总和,如果结果溢出 long,则引发异常。
抛出:
ArithmeticException
- 如果结果溢出很长时间
关键的观察结果是,cumSeconds
是long
的cumSeconds < Long.MAX_VALUE
只有在cumSeconds
正好Long.MAX_VALUE
时才能为假。
如果您使用长数字进行计算,则需要相当长的时间才能准确达到此值(如果曾经达到过(,因为当您离开数字范围时,长算术会环绕。
当双精度值足够大时,使用双精度数进行算术将产生最大值。
@rgettman已经详细介绍了当您使用double
而不是long
时发生的圆形体操。但还有更多。
当您反复向long
添加大量数字时,您最终会得到负数。例如,Long.MAX_VALUE + 1L = Long.MIN_VALUE
.发生这种情况时,您将无限期地重复该过程。
因此,如果您将代码更改为:
while (cumSeconds >= 0L) {
// 31557600000 is the number of milliseconds in a year
cumSeconds += 31557600000L;
你会发现事情变得消极的地方,因为cumSeconds
翻身了。