aarush._.prasad
aarush._.prasad

Reputation: 19

When does System.nanoTime() equal 0 or when does it reset?

I am doing some work with System.nanoTime().

I found online that if two events happen, A and B, such that A precedes B, then System.nanoTime() at A is less than that of at B. I know that the actual value of System.nanoTime() is always irrelevant, and we only ever subtract two System.nanoTime() values to obtain elapsed time. However, the first observation implies that System.nanoTime() is always increasing. Since we can always store it in a long in Java (which is a finitely sized data type), at one point System.nanoTime() must reset and equal 0. When does this happen, and what happens if you try measuring the elapsed time starting from before the reset and ending after the reset?

I've coded some snippets to try and find when System.nanoTime() = 0, but I got nothing. It surely can't always be increasing, right?

Upvotes: 0

Views: 232

Answers (2)

yshavit
yshavit

Reputation: 43401

Your question seems to imply that there are "borders" on the number line — specifically, 0 — that affect the math. That isn't the case, with one small caveat (which I'll describe at the bottom of this answer, and the JavaDocs also mention).

Let's think about two regions of the number line:

  1. "normal" numbers
  2. very big numbers (ie, numbers close to Long.MAX_VALUE)

Remember that just about the only thing you can do with nanoTime() is compute delta = end - start. So, let's look at what happens in these various regions.

Normal numbers

Most of the time, the math "just works" without any trickery. For example, if we start at t=1000 and end at t=1005, then we subtract them out and get 5. Note that this also works in the negative region (-1000 - (-955) = 5), and also when the start is negative and end is positive.

That's all easy enough.

Very big numbers

Things get a bit interesting when numbers get close to Long.MAX_VALUE. Let's say start = Long.MAX_VALUE - 1:

  • 1 ns later, we're at Long.MAX_VALUE
  • 2 ns later, we wrap around to Long.MIN_VALUE
  • 3 ns later, we're at Long.MIN_VALUE + 1
  • 4 ns later, we're at Long.MIN_VALUE + 2
  • 5 ns later, we're at Long.MIN_VALUE + 3

(That wrap to MIN_VALUE is the "reset" you mentioned in your question; note that it wraps to MIN_VALUE, not to 0.)

(Also, I'm not counting them out to be patronizing. It's easy for me to get confused, and for small deltas like 5, it's easier for me to just count it out than write out the formulas!)

So:

delta = end - start
      = (Long.MIN_VALUE + 3) - (Long.MAX_VALUE - 1)
      = Long.MIN_VALUE + 3   - Long.MAX_VALUE + 1
      = Long.MIN_VALUE       - Long.MAX_VALUE + 4
      = -2^63                - (2^63 - 1)     + 4
      = -2^63                - 2^63       + 1 + 4
      = -2^64 + 5

This number is too small to represent in a 64-bit integer, but the way twos complement and wrapping works, it wraps to 5. Basically, the twos complement of -264 is a 1 and then 64 0s. This gets truncated to the lowest 64 bits, which are all 0; so -264 wraps to 0, and you're left with just the +5. That's not exactly what's going on in the computer, but the math works out the same.

You can test this out yourself to see that it works:

long start = Long.MAX_VALUE - 1;
for (int i = 0; i < 20; i++) {
    long end = start + i;
    long delta = end - start;
    System.out.printf("expect %d, saw %d (end=%d)%n", i, end - start, end);
}

(live example: https://ideone.com/IFYBhW)

The caveat

Unfortunately, that twos complement wrapping will only get you so far. As time ticks and that "+5" grows, eventually it toggles the most significant digit of the long, and the result becomes 10000....0, which is Long.MIN_VALUE. In other words, after a long enough time, end - start will actually give you a negative answer, which is obviously incorrect. (An equivalent and potentially simpler way to look at this: 263 is bigger than what a long can represent, so when the delta reaches this, it wraps around to MIN_VALUE.)

Luckily for you, this takes 263 ns, which is a very long time: about 292.28 years. This is the origin of possibly my favorite piece of documentation in any piece of software:

Differences in successive calls that span greater than approximately 292 years (263 nanoseconds) will not accurately compute elapsed time due to numerical overflow. (source)

I can just imagine a PM telling an engineer at Sun, "you have to document all edge cases", and the engineer telling the PM "263 nanoseconds is huge, it's not worth documenting", and them going back and forth until the engineer lets out a sigh and fires up a terminal:

$ units

You have: 2**63 nanosecond
You want: year
        * 292.27727
        / 0.0034214088

"hrmph!"

Upvotes: 2

Basil Bourque
Basil Bourque

Reputation: 340040

Your questions are addressed in the documentation. See the Javadoc for System.nanoTime. To quote:

The value returned represents nanoseconds since some fixed but arbitrary origin time (perhaps in the future, so values may be negative).

The origin time is arbitrary. That means you cannot ask when the value will be zero. The documentation is telling you not to expect the origin time.

Furthermore, that quote warns you that the values may be negative or positive. No promises are made either way.

The only promise made to you is that the value will always be increasing, incrementing to a greater number. The increases continue until reaching the limit of a long. That 64-bit value is Long.MAX_VALUE. At that point, the values wrap around back to Long.MIN_VALUE.

So, to be safe, you shield compare your start and end times. If your start is larger than your end, then you know a wraparound occurred.

In practice, I have seen only positive numbers in the various implementations of Java I have used on various host OSes. And I have seen the origin time set to zero going back to when the machine booted or the JVM launched (I don’t recall which). I only see relatively small positive numbers. That means over a century of time before a wraparound.

If my observations hold, then for casual testing and logging, you need not worry about wraparound. But for anything critical, you do need to be prepared for wraparound because my observed behavior is not promised.

You asked:

I've coded some snippets to try and find when System.nanoTime() = 0, but I got nothing. It surely can't always be increasing, right?

Upon the value reaching Long.MAX_VALUE, the count can go no higher. That is the limit of a long, a 64-bit signed integer.

I presume that upon reaching that limit the nanoTime implementation would proceed from Long.MAX_VALUE to Long.MIN_VALUE, and keep on counting upward. But the Javadoc does not explicitly say that. A peek at the OpenJDK source code might be illuminating.


Regarding the ranges of long number minimum and maximum, while the range of 64-bit numbers can represent about 584 years (292 + 292), a count of elapsed nanoseconds is limited to 292 before enchanting integer overflow.

This is curious, but unimportant. None of use will be tracking elapsed time over 292 years.

And handling the wraparound when the nanoTime value reaches Long.MAX_VALUE and presumably continues at Long.MIN_VALUE is a different issue altogether. (However, some smart people commenting on this page disagree. But I believe them to be misreading the text.)

The Javadoc notes this fact:

Differences in successive calls that span greater than approximately 292 years (263 nanoseconds) will not correctly compute elapsed time due to numerical overflow.

Duration durationPositive = Duration.ofNanos ( Long.MAX_VALUE );
long daysPositive = durationPositive.toDays();
double yearsPositive  = daysPositive/365.25;
System.out.println ( "durationPositive = " + durationPositive );
System.out.println ( "yearsPositive = " + yearsPositive );

Duration durationNegative = Duration.ofNanos (Long.MIN_VALUE  );
long daysNegative = durationNegative.toDays();
double yearsNegative  = daysNegative/365.25;
System.out.println ( "durationNegative = " + durationNegative );
System.out.println ( "yearsNegative = " + yearsNegative );

durationPositive = PT2562047H47M16.854775807S

yearsPositive = 292.26830937713896

durationNegative = PT-2562047H-47M-16.854775808S

yearsNegative = -292.26830937713896

Upvotes: 2

Related Questions