Timer float not evaluating correctly when I check if it's less than another number
I have a timer for some code that runs every FixedUpdate. The code looks like this:
float timer = 0;
if (timer <= 0.2f)
{
//do stuff
timer += Time.fixedDeltaTime;
{
I've used timers like this many times, with no issues. But this time I've noticed that the loop performs an extra iteration, because when the timer reaches a value of 0.2, it is evaluating as being greater than the 0.2f it's being compared to.
This appears to be because of the inaccuracy of floating point numbers. But, all the documentation I've been able to find suggests that this is the accepted approach to timers, but this must be a problem that crops up often. Is there some more accurate way of using a timer that is accepted as the better practice?
probably unrelated, but should if (timer <= 02.f)
be if (timer <= 0.2f)
Your description makes it sound like you want it to be 1/5th of a second(0.2f). Your code looks to be 2 seconds(02.0f). Is this intentional or just a simple reading error?
This is really weird, I use similar code all the time and it works as expected.
Could you post some more code (preferably by editing your question) to see more context? I would like to understand what is happening, and of course, help.
Another question: have you tried moving the timer += Time.fixedDeltaTime;
outside the condition and see what happens?
Answer by ransomink · Feb 19, 2018 at 10:30 AM
You can do at least two different approaches: The first one rounds the float to two decimal places to avoid floating point imprecision.
private int _iteration;
private float _timer = 0f;
void FixedUpdate()
{
if ( _timer < 0.2f )
{
_iteration++;
_timer += Time.fixedDeltaTime;
_timer = ( float )System.Math.Round( _timer, 2 );
Debug.Log( "Iteration: " + _iteration + " | Timer: " + _timer );
}
}
The second one uses Mathf.Approximately to compare two floats using Epsilon as a tolerance.
private int _iteration;
private float _timer = 0f;
void FixedUpdate()
{
if ( !Mathf.Approximately( _timer, .2f ) )
{
_iteration++;
_timer += Time.fixedDeltaTime;
Debug.Log( "Iteration: " + _iteration + " | Timer: " + _timer );
}
}
I've tested both versions and it works fine. I believe the problem is the fixedDeltaTime
itself because it will always increment the same value. During my test, some values, like 1, was listed as 0.99999~ and so on. This was before and why I added rounding, so that's why you're getting an extra iteration. Even though fixedDeltaTime increments at a constant rate it has floating point imprecision; So ( timer < .2f )
will run again even when timer is equal to .2f...
Answer by HenryStrattonFW · Feb 18, 2018 at 08:42 AM
Have you tried separating the tests, so the < can be done as currently but instead of = test using Mathf.Approximately. This may just be an issue with floating point imprecision.
Even if I remove the equality entirely, and just do timer < 0.2f, it does not evaluate correctly (when the timer is also 0.2f). It does one more iteration and only evaluates as being greater than when the timer is 0.22f.
It does evaluate correctly if I cast both the timer and the 0.2f to decimal inside the if statement.
Well if you remove the equality check then it will and should to one more iteration as 0.2f is not less than 0.2f
Answer by Harinezumi · Feb 19, 2018 at 03:11 PM
Reading ransomink's answer I've just realised that the real issue is that you ( @sdcur2 ) would like it to execute what you have in the block an exact number of times. If this is the case, a better solution is to use an exact counter, an int:
private int iterationCounter = 0;
private int numIterations = 10;
private void FixedUpdate () {
if (iterationCounter < numIterations) {
iterationCounter++; // I recommend to first increment the condition and only after do things to make sure that the condition variable is updated
// do stuff;
}
}
Using a float
for timers is accepted in other cases, because there you care less about the number of times, you care about the duration and the progress of the variable (e.g. in case of lerping).
It's a situation where I do care about duration - it's an animation that I want to last exactly a fifth of a second. In general, I always try to use variables rather than hard-coding that sort of thing, in case the value changes in future. But I guess using fixedDeltaTime will always be in increments of 0.02 seconds, and I should just treat it as an invariable number - which means I could just specify an exact number of times.
Then I did not understand the issue after all :D
Working with time and floats on a computer you will never be able to execute things exactly, you will always have to expect imprecision, and clamp the values or compare approximately.
Btw, you can actually change fixedDeltaTime in the Editor, in Edit -> Project Settings -> Time -> Fixed Timestep setting, but it is not recommended.