如何比较两个纳秒时间值?【javadoc混淆】

20

我已经阅读了System.nanoTime()的javadoc,一切都很清楚。直到我看到最后一段:

要比较两个nanoTime值

long t0 = System.nanoTime();

...

long t1 = System.nanoTime();

应该使用t1 - t0 < 0而不是t1 < t0,因为存在数值溢出的可能性。

有两件事对我来说不太清楚:

  1. 如果t1t0之后被取走,为什么还要检查t1 < t0呢?我的理解是纳秒时间总是增长的,所以我更想检查t1 > t0
  2. 假设这是一个笔误,并且他们的意思是正确的检查方式是t1 - t0 > 0。我仍然不明白这是正确的检查方式,而不是t1 > t0。他们提到数值溢出,我不太明白他们的意思。关于数值溢出,以下是提到的内容:

跨越大约292年(2^63纳秒)的连续调用之间的差异将由于数值溢出而无法正确计算经过的时间。

好的,因此由于纳秒时间被储存为long值,它最终会在292年后溢出。接下来会发生什么?它会从开头开始计时,也就是最低负值-2^63吗?或者它停止计时并始终返回(2^63 - 1) ?


2
数值类型的溢出总是相同的。例如,Integer.MAX_VALUE + 1 = Integer.MIN_VALUE - SomeJavaGuy
1
这只是一个权宜之计... 584年后即使t1-t0<0也不再适用 ;) - ParkerHalo
2
@ferrybig,那不是真的。正如Javadoc所指出的那样,纳秒时间使用一个固定的原始时间,与系统时间无关,并且在JVM终止之前不会更改。随着系统时钟变化而变化的是System.currentTimeMillis()。当存在数字溢出时,纳秒时间才会返回一个“更早”的时间,这已经在大多数答案中得到了解释。 - Evgeni
4个回答

10

你说得对,你引用的文档部分似乎有点混淆。

然而,重要的文档部分是这样的:

此方法仅用于测量经过的时间,与任何其他系统或挂钟时间概念无关。返回的值表示自某个固定但任意的原始时间(可能在未来,因此值可能为负)以来的纳秒数。同一原始时间由Java虚拟机实例中所有调用此方法的虚拟机实例使用; 其他虚拟机实例可能会使用不同的原点。

(我加粗了重点.)

这意味着您没有任何保证,可以保证您的代码不会恰巧在产生的长整数值从正数翻转为负数的时间运行。这将在300年后发生并没有任何保证,它今天可能就会发生。

目前,我的JVM返回类似于3496793269188的数字,但如果愿意,它可以返回非常接近9223372036854775807(即Long.MAX_VALUE),这将使从正数到负数的翻转即将发生。

因此,您应采取所有必要的预防措施。


Long.compare(t0, t1)并没有处理溢出的情况。 - user2357112
@user2357112 O_O 你说得对,我本来以为它可以,结果发现它不行,现在想想,它也不可能行。唉!我不知道为什么要提到它。我现在就把它删掉。 - Mike Nakis
1
这个答案以及其他解释溢出算术的答案帮助了我,现在一切都清楚了。谢谢 :) - Evgeni

7

好的,javadoc 是真实的。考虑以下例子:

        long t0 = Long.MAX_VALUE;
        long t1 = Long.MIN_VALUE;

        System.out.println(t1 < t0);
        System.out.println(t1 - t0 < 0);

它会给出:
true
false

虽然从数学上来看两种表达式都是正确的。但在我们的情况下,我们知道time的负值意味着它已经溢出了,因此应该真正地比正数更大。


2
当 t1 在 t0 之后被取走(在现实世界中,t1 < t0 = false),并且 t1 发生了溢出(而不是 t0)。在这种情况下,t1 为负数,t0 为正数,因此 t1 - t0 < 0 的结果为 false,因为二进制运算符 '-' 不会犯溢出错误,而 t1 < t0 将给出错误的结果:true。
更基本的是,如果将一个负数读作无符号数,则其值大于有符号的正数:https://en.wikipedia.org/wiki/Signed_number_representations

2
让我从您的问题结尾开始回答。如果值大于2^63-1,那么该值将会溢出。数字溢出的常见实现方式是存储结果中最不重要的可表示位;该值将会 "wrap"(请参考我在溢出方面的帖子底部)。
序列如下:2^63-2、2^63-1、-2^63、-(2^63-1)...... 现在回顾一下 Javadoc,我同意使用比较的解释很令人困惑,并且自然而然地我们会尝试比较t1>t0以验证是否t1发生在t0之后。你有一部分是对的。虽然我认为这不是笔误,但解释不正确。我认为它应该这样说:
对于两个值t0t1(其中t1是在t0之后捕获的),您不应使用t1<t0(来检查false),而应该使用t1-t0<0,类似地,您不应使用t1>t0(来检查true),而应该使用t1-t0>0
或者正式表述为:
对于两个“纳秒”值t0t1,其中t1t0之后被捕获,如果t1t0的间隔<= 2^63 ns,则以下内容适用:(t1-t0>0)==true
为什么?因为即使t1溢出(所以它是负数),结果t1-t0也将是负数,但它将小于-2^64,并且会“溢出回来”变成正值。
只要满足关于t0t1之间距离的条件,上述内容就适用!如果距离大于2^64,例如:对于t0=1;t1=-(2^64-2),减法结果将是:t1-t0=-(2^64-1),因此指定的条件(t1-t0>0)将给出不正确的结果。
明白吗?)
关于溢出:
为了说明问题,假设一种使用8位存储的类型(而不是long使用的64位),因此二进制转十进制表示为:
0000 0000 => 0
0000 0001 => 1 (2^0)
0000 0010 => 2 (2^1 + 2^0)
...
1111 1111 => 255 (2^7 + 2^6 + ... + 2^1)

现在下一个数字自然是当你增加1时产生的。 将二进制1111 1111加1,将产生1 0000 0000。 (1) 0000 0000 => -256 !!! 通常,第8位上的溢出位表示(负权重)符号位。
first following value is adding 1 to most-right position 
(1) 0000 0001 => -256 + 2^0 = -255
(1) 0000 0010 => -256 + 2^1 = -254
(1) 0000 0011 => -256 + 2^1 + 2^1 = -253
...

你明白了

这个问题的根源在于底层硬件注册表实现依赖于二进制值。你可以在这里阅读更详细的解释:http://www.allaboutcircuits.com/textbook/digital/chpt-2/binary-overflow/


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