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?

Yes, sorry, that was a typo. I've corrected it to 0.2f

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?

You could turn the colors you want into variables using this, https://docs.unity3d.com/ScriptReference/Color-ctor.html, and then use those variables in the setcolor script. You'll have to find out the float values of the colors you want, and i'm not good with color palettes and integrating them into rgb values, so I won't be much help telling you how to find the colors you want, but i'm sure there's some article out there that can provide you the information you need. :)

3 Answers

3

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...

@sdcur2 Did it work for you?

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.

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.