37
\$\begingroup\$

As it is said here :

Time.time

The time at the beginning of this frame (Read Only). This is the time in seconds since the start of the game.

And as I know the time is stored in float. So, my question is what will happen when the value of time becomes very large? Can it overflow? Will it lose precision and cause bugs? Will the game just crash or what?

\$\endgroup\$
  • 6
    \$\begingroup\$ Just for context, see the Guinness World Record on Longest videogame marathon. \$\endgroup\$ – Theraot May 30 '17 at 18:06
  • 5
    \$\begingroup\$ @AlexandreVaillancourt I think there's a useful question here if we interpret it as "Are there problems when Time.time gets very large?" as opposed to focusing literally on "overflow" alone. I've edited the question along these lines to try to address your feedback. \$\endgroup\$ – DMGregory May 30 '17 at 18:07
  • 2
    \$\begingroup\$ @DMGregory But the question was answered and accepted already... I agree your edits make for a more useful question, just a bit late. \$\endgroup\$ – MichaelHouse May 30 '17 at 18:16
  • \$\begingroup\$ I tried to phrase the edit so that the accepted answer is still a correct answer to the edited version. (After all, it already talks about precision issues) I wouldn't take offense if it's rolled back though. \$\endgroup\$ – DMGregory May 30 '17 at 18:19
63
\$\begingroup\$

There is a real risk to loss of time accuracy from using a single-precision float.

It will still retain accuracy to the nearest second for 97 days. But in games we often care about accuracy on the order of a frame duration.

A single-precision float time value in seconds starts to lose millisecond accuracy after about 9 hours.

That's already not outside the realm of possibility for a single play session, or leaving the game running "AFK" while you're at work/school or sleeping. (This is one reason why a common way to test a game is to run it overnight and check that it still plays correctly in the morning)

On modern consoles, where games are often suspended and resumed between play sessions rather than shut down entirely, it's not unexpected for a "single session" from the game's eye view to exceed 9 hours of runtime even when played in short bursts.

Assuming Unity's deltaTime is computed from a higher-precision time source, which is borne out by Peter's experiments in another answer, relying on deltaTime for frame-scale timings is relatively safe (just be cautious of accumulating very long durations by summing deltaTime values - adding small increments to a larger float is a classic recipe for precision loss, even when you compensate with savvy algorithms).

Since fixedDeltaTime keeps the same value you set, rather than dynamically changing from frame to frame, you can also put timing-sensitive behaviour in FixedUpdate to get stronger guarantees of consistency. deltaTime will automatically return the appropriate fixed delta in these methods. While there can be a beat frequency between fixed & frame updates, you can smooth this out through interpolation.

What you want to avoid is computing durations by subtracting one timestamp from another. After hours of play this can cause catastrophic cancellation, leading to far less precision than you get early in the run. If comparing timestamps is important to your systems, you can generate your own higher-resolution time value using other methods, like System.Diagnostics.Stopwatch instead of Time.time.

\$\endgroup\$
  • \$\begingroup\$ Unreal also chooses to expose game time as a single-precision float, in both code and blueprints. You can work around it with timestamps in realtime and doing your own time dilation, the same as you'd do in Unity. (Just watch out that Unreal's timestamp type is based on the 32-bit epoch). Extensions do exist for C# in Unreal if you so choose. \$\endgroup\$ – DMGregory Jun 11 '17 at 11:51
  • \$\begingroup\$ For [ms], you start loosing precision around 16 484 - 32 768 band. That is, you may loose precision after 4h30min, after 9h you surely cannot trust that. \$\endgroup\$ – xmedeko Apr 11 at 8:27
  • \$\begingroup\$ Technically between 4.5-9 hours, you have precision to within 0.98 milliseconds - I chose to flag the point at which the error exceeds 1.00 milliseconds. But the point is well taken - precision degrades all along the way, so code that needs more precision may start to misbehave even earlier. \$\endgroup\$ – DMGregory Apr 11 at 11:31
16
\$\begingroup\$

The maximum value of float is 3.40282347 * 10^38, which equals 10^31 years when measured in seconds (or 10^28 years when measured in milliseconds). Trust me, it won't overflow.

The only thing that may appear is inaccuracy. But a single precision floating point number has an accuracy of 7-8 decimal digits. If using it to measure seconds, it is accurate for about 194 days. If measuring milliseconds it's accurate for only 4,5 hours. So it completely depends on the accuracy you need and you may need to find alternative ways if you need to be accurate to the millisecond (which you probably don't).

\$\endgroup\$
  • 7
    \$\begingroup\$ Wolfram Alpha provides a tone-setting idea of how big a number of years that is that might be worth mentioning: that's approximately 20 magnitudes higher than the current age of the universe. Not twenty times higher, but the current age plus twenty more zeroes. \$\endgroup\$ – doppelgreener May 30 '17 at 17:16
  • 1
    \$\begingroup\$ @Draco18s Depends on the way unity measures deltaTime, but if they take a time point for each frame and subtract those two I suppose the answer is a definite yes. \$\endgroup\$ – LukeG May 30 '17 at 17:20
  • 7
    \$\begingroup\$ This answer is not right at all. You can run an experiment. You may increment float one by one in the loop. After reaching 10 000 000 it will stop incrementing. The reason is you can not add small numbers to big numbers correctly when you are dealing with float. The right answer was given by a @DMGregory \$\endgroup\$ – Seagull May 30 '17 at 20:10
  • 2
    \$\begingroup\$ @Seagull Nowhere I write about incrementing. It's a fact that the maximum number representable by a float is (about) 3,4 *10^38, so that rules out overflowing in the strict sense (as meant by the OP). I don't see why the answer would be incorrect as is stands? \$\endgroup\$ – LukeG May 30 '17 at 20:20
  • 2
    \$\begingroup\$ @Seagull I think LukeG's answer is correct (even prior to the improvements in the edits) - his answer and mine just refer to different classes of needs. I like geeking out about float precision and limiting cases, while LukeG's looks at the issue a bit more broadly, with less emphasis on exacting precision needs. \$\endgroup\$ – DMGregory May 30 '17 at 20:23
12
\$\begingroup\$

How much precision does your game need?

A 32-bit float has 24 bits of precision. In other words, at time t, the precision is ±2-23 × t. (This isn't exactly correct, but it's close, and the exact details aren't terribly interesting.)

  • If you need 1ms precision, then after 1ms ÷ 2-23 = 8400 s = 2h 20m, a 32-bit float is no longer acceptable. A precision of 1ms is not necessary for most games, but it is considered necessary for musical applications.

  • If your game runs on a 144 Hz monitor, and you want frame-accurate graphics, then after 1 ÷ 144 Hz ÷ 2-23 = 58000 s = 16h, a 32-bit float is no longer acceptable. 16h is a long time to run a game, but it's not inconceivable. I bet a significant percentage of us have run a game for 16 hours in a single stretch--even if just because we left the game running and got some sleep. At that point, animations and physics updates would be reduced to below 144Hz.

If Unity's Time.deltaTime is calculated from a higher-precision clock, then you could use that and still get full precision. I don't know if this is true or not.

Side Note

Games and other programs on Windows often use GetTickCount() to figure out the time. Since it uses a 32-bit integer to count milliseconds, it wraps around about every 50 days, if you leave your computer on that long. I suspect that many games would hang, crash, or misbehave in bizarre ways if you were playing them at the 50-day mark.

Integers, like Windows' GetTickCount(), have a risk of overflow, whereas floats, like Unity's Time.time, have a risk of losing precision.

\$\endgroup\$
11
\$\begingroup\$

The other answers only speculate, so I wrote a simple Unity project and let it run for a while. After a couple hours this is the result:

  • UnityEngine.Time.time looses accuracy rather quickly. As expected, after running the game for 4 hours the values jump by 1 millisecond, and the inaccuracy does increase after that.
  • UnityEngine.Time.deltaTime keeps its initial sub-millisecond accuracy even after Unity has been running for hours. Thus it's virtually guaranteed that deltaTime is derived from a platform-dependent high resolution counter (which usually overflow after 10-1000 years). There remains a tiny risk that deltaTime will still lose precision on some platforms.

The inaccuracy of Time is an issue for everything that depends on UnityEngine.Time.time. One specific example is rotation (e.g. of a windmill), which is usually based on UnityEngine.Mathf.Sin(UnityEngine.Time.time*rotationSpeed). Even more of an issue is _Time which is explicitly used for animations by shaders. How high does the inaccuracy need to be before it starts to be noticable? 20-30 ms accuracy (2 days) should be the point where people who are aware of the issue, and pay close attention, will notice. At 50-100 ms (5-10 days) sensitive people who don't know about the problem will start figuring it out. From 200-300 ms (20-30 days) the problem will be obvious.

So far I haven't tested the accuracy of _SinTime,_CosTime, and Unity_DeltaTime, so I can't comment on these.

This is the Script I've used for testing. It's attached to a UI.Text element, the Player Settings of the project allow the project to run while in the background, and VSync in Quality Settings is set to Every Second V Blank:

public class Time : UnityEngine.MonoBehaviour {
    void Start () {
        LastTime = (double)UnityEngine.Time.time;
        StartTime =  System.DateTime.Now;
        LastPrintTime =  System.DateTime.Now;
    }
    double LastTime;
    System.DateTime StartTime;
    System.DateTime LastPrintTime;
    void Update () {
        double deltaTimeError = (double)UnityEngine.Time.deltaTime - ((double)UnityEngine.Time.time - LastTime);
        if (System.DateTime.Now - LastPrintTime  > System.TimeSpan.FromMilliseconds(1000))
        {
            GetComponent<UnityEngine.UI.Text>().text = "StartTime: " + StartTime.ToLongTimeString()
                + "\nCurrentTime: " + System.DateTime.Now.ToLongTimeString()
                + "\n\nTime.deltaTime: " + UnityEngine.Time.deltaTime.ToString("0.000000")
                + "\nTime.time - LastTime: " + ((float)(UnityEngine.Time.time - LastTime)).ToString("0.000000")
                + "\nAbsolute Error: " +  System.Math.Abs(deltaTimeError).ToString("0.000E0");
            LastPrintTime = System.DateTime.Now;
        }
        LastTime = UnityEngine.Time.time;       
    }
}

enter image description hereenter image description hereenter image description here enter image description here

If you're wondering about the 0.033203 value, I'm pretty sure that's actually 0.033203125, which has a binary floating point representation of 00111101000010000000000000000000. Notice the many 0s in the mantissa.

Workarounds

Most genres do not need a workaround. Most players won't even play a game for 100 hours total, so investing money to fix a problem people will only notice after a hypothetical few days of continuous playtime is economically not viable. Unfortunately I cannot come up with a simple universal workaround that fixes the entirety of the problem without carrying along some significant drawbacks.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.