A simple number incrementation logic isn't working.

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
entua
Prole
Posts: 3
Joined: Sun Oct 17, 2021 6:32 pm

A simple number incrementation logic isn't working.

Post by entua »

Here's the piece of code:

Code: Select all

local d = 0
local z = 0.2

function love.load()
    return nil
end

function love.update()
    d = d + z
    if d == 2 then
        z = -0.2
    end
    print(d)
end

function love.draw()
    return nil
end
The program is supposed to increase the variable d by 0.2 every frame, and as soon as it reaches 2, it should "reverse" and start decreasing it by 0.2 every frame.

And well, it ain't working. The console logs show that the program skips the "reverse" moment and just keeps increasing d by 0.2 ad infinitum.

For investigation purposes, I tried changing the if d==2 to if d >= 2. And the console log revealed the problem immediately: the program kept increasing d up to 2, then it went to 2.2, and only after that it started decreasing it.

And now I'm looking at the code like an idiot and just can't understand what a hell is going on. Suppose that d is at 1.8. On the next frame, it's going to be incremented by 0.2 and become equal to 2. Immediately after that, it's gonna run the if d == 2 test, which will return true because it's already 2 at this moment. Which will immediately set z to -0.2. By the start of the next frame, d is gonna be equal to 2, and z will be equal to -0.2, which means that it SHOULD start going down immediately.

So what is this? Is Lua "asynchronous" or something like that, and starts running the next frame before it has finished the previous one? Or does it not actually update variable before the next frame begins?

So far the only way I can achieve the desired effect is by setting if d >= 1.8. In this case, yes - it will go to 1.8, then up one more step to exactly 2, and then go down. However, it just sounds like a bad coding practice, especially since I need to keep z variable as adjustable.
User avatar
BrotSagtMist
Party member
Posts: 659
Joined: Fri Aug 06, 2021 10:30 pm

Re: A simple number incrementation logic isn't working.

Post by BrotSagtMist »

My guess is 2 is a hard int, 2.0 is a float. Incrementing in steps of .2 may simply never lead to exactly 2.
Try 2==math.floor(d) instead.
But actually this kind of comparison should not be used at all because as you have seen, it will fail too often.
obey
MrFariator
Party member
Posts: 559
Joined: Wed Oct 05, 2016 11:53 am

Re: A simple number incrementation logic isn't working.

Post by MrFariator »

Lua's numbers are double-precision floating point numbers. If you are at all familiar with how floating point numbers work, you'll likely realize that summing two numbers together is not always always going to be what humans intuit. We can intuit that 1.8 + 0.2 is 2, but the computer could introduce a rounding error and see it as 2.000000001, which is not exactly equivalent to 2. As long as you deal with decimal numbers, and not integers, you'll have to keep this in mind whenever you touch any floating point arithmetic.

On my computer your code runs fine (the accuracy of floating point operators can be hardware dependent, though most modern processors should be IEEE 754 compliant), but I'd still advice to use ">=" in place of "==" when dealing with these sorts of situations.
Last edited by MrFariator on Tue Feb 01, 2022 10:03 pm, edited 1 time in total.
User avatar
dusoft
Party member
Posts: 655
Joined: Fri Nov 08, 2013 12:07 am
Location: Europe usually
Contact:

Re: A simple number incrementation logic isn't working.

Post by dusoft »

Please, use support forum for this kinds of questions.
entua
Prole
Posts: 3
Joined: Sun Oct 17, 2021 6:32 pm

Re: A simple number incrementation logic isn't working.

Post by entua »

MrFariator wrote: Tue Feb 01, 2022 9:57 pm I'd still advice to use ">=" in place of "==" when dealing with these sorts of situations.
Yep, that's what I usually do. However, I noticed that the game behaviour doesn't add up and decided to investigate. That's when I noticed that "if d >= 2" reverses incrementation only after d becomes 2.2, which shouldn't happen. And it screws my entire program, because I need d to go precisely up to 2 and then go down. Why could that problem happen?

P.S. I've just tried "if math.floor(d) == 2" and it worked exactly the same as "if d >= 2": the reverse happened, but only after d went up to 2.2. Which means that you are correct about 2 being some 2.00000001 float under the hood. But then, why does it wait for d to become 2.2 before it reverses the incrementation?

P.P.S. I did some further investigating by running the following code:

Code: Select all

local d = 0
local z = 0.2

function love.load()
    return nil
end

function love.update()
    d = d + z
    if math.floor(d) == 2 then
        z = -0.2
    end
    print(d)
    print(math.floor(d) == 2)
    print(z)
end

function love.draw()
    return nil
end
I used the "math.floor(d) == 2" condition and added more console logs to keep better track of what's going on. This is the log that I got:

Code: Select all

1.8
false
0.2
2
false
0.2
2.2
true
-0.2
2
false
-0.2
So basically, on the frame when d becomes 2, you can see that the "math.floor(2) == 2" returns false and z stays unchanged.

I dunno... could it be that 2 is actually 1.9999999999999999999 under the hood, not 2.00000000000001?
MrFariator
Party member
Posts: 559
Joined: Wed Oct 05, 2016 11:53 am

Re: A simple number incrementation logic isn't working.

Post by MrFariator »

Very possible, and likely what's happening in your case. Floating point rounding errors basically round out the number to the closest approximation the floating point format can represent, so it can give or take to either direction - depending on the size of the number. There's always a certain "minimum step" a floating number can take around its current value, and that step is much finer around 0, but much coarser for really huge numbers (ie. the further away you get from 0, the minimum increment becomes increasingly bigger).

I do still think it's a bit peculiar that your console output shows a clear "2", and when I run similar code it behaves like you'd initially expect. I can mostly brush it up to some hardware shenanigans.
Last edited by MrFariator on Tue Feb 01, 2022 11:34 pm, edited 1 time in total.
User avatar
darkfrei
Party member
Posts: 1204
Joined: Sat Feb 08, 2020 11:09 pm

Re: A simple number incrementation logic isn't working.

Post by darkfrei »

Code: Select all

function areFloatsSame (a, b, tolerance)
	if math.abs(a-b) <= tolerance then
		return true -- almost same values
	end
	return false
end

Code: Select all

local bool = areFloatsSame (2, 1.999999999, 0.015625) -- true
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3674
Joined: Sun Oct 18, 2015 2:58 pm

Re: A simple number incrementation logic isn't working.

Post by pgimeno »

Without a special format, Lua hides the last few digits when printing numbers. You can print the actual number that Lua is using with:

Code: Select all

print(string.format("%.17g", number))
That conversion is lossless: you can convert the value printed to a float number and they should compare equal.

The problem here is that when converted to binary, the decimal number 0.2 is periodic; to be precise it equals 0.00110011001100110011001100110011...

Periodic numbers can't be represented exactly because they are infinite, therefore they are truncated, to 52 binary places after the dot in this case because that's what double-precision floats use, which is what Lua in turn uses.

When adding these numbers, the rounding does not always go in the right direction, and you don't always get 2.0.

There are several possible solutions for this. One is to use a number that isn't periodic in binary. That means using fractional numbers with a power of 2 in the denominator; for example, 0.203125 = 13/64. If you compare the end result to 2.03125, it will be equal, because every sum will be exact.

Code: Select all

local d = 0
local z = 0.203125

function love.update()
    d = d + z
    if d == 2.03125 then
        z = -0.203125
    end
    print(d)
end
However, 0.2 = 1/5 and 5 is not a power of 2, therefore that fraction is periodic in binary and you aren't guaranteed to get exact results, except by pure luck. For example:

Code: Select all

print(0.2 + 0.2 == 0.4) -- prints true
print(0.2 + 0.2 + 0.2 == 0.6) -- prints false
print(0.2 + 0.2 + 0.2 + 0.2 == 0.8) -- prints true
The integers are the particular case where the fraction has 1 = 2^0 in the denominator. 2^0 is a power of 2 therefore they are not periodic in binary, which was obvious anyway but that's another way to look at it :)

A more flexible solution is to loop using integers, for example starting from 0 and ending at 10, which is the number of iterations that the loop would do, and then multiply that by 0.2 to get every intermediate step.

Code: Select all

local d_int = 0
local z = 1

function love.update()
    d_int = d_int + z
    if d_int == 10 then
        z = -1
    end
    local d = d_int * 0.2
    print(d)
end
User avatar
darkfrei
Party member
Posts: 1204
Joined: Sat Feb 08, 2020 11:09 pm

Re: A simple number incrementation logic isn't working.

Post by darkfrei »

Some languages store not 0.2, but 2/10 and after 2/10+2/10 get exactly 4/10
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests