GML Why is it necessary to do >= or <= rather than == sometimes?

G

Guest

Guest
I think I've read that it's best practices to use >= or <= in some counting or looping situations rather than == because it can't be guaranteed that the computer will exactly hit the number you want.

I ran into this today in the form of a bug that appears only in a YYC build and not the VM. (@rwkay *ahem*) Normally I handle animations by using some variation of this code:

Code:
if (image_index >= (image_number - 1)) // let animation finish
    {
    // proceed
    }
In one of my new scripts, however, I didn't do this because I was syncing up a specific mid-animation frame with another instance's animation, and I simply forgot about the best practice. It worked fine in the VM, but in YYC, my actor got stuck in an animation loop and wouldn't move on. I changed two times where I did == image_number to >= image_number and it was fixed.

Is this a difference between the VM and Java (this was Android YYC)? Or is it a difference between a more powerful CPU and my phone's CPU?

I'm just curious to know the underlying reason.
 

RangerX

Member
Its because everything is in fact, a float. Then engine will round and floor and ceil tons of stuff in its calculations and if you use floats yourself on top of it, its rare that a variable will contrain a pure integer.
So yeah, its better to be safe then sorry. Use "==" when you know that the number you compare is exact. As for why you have different results in different compilers its because the YYC compiler treats with a different language than the regular compiler. From one programming language to another there can be some very small differences in calculations and features -- which in turn might change your expected behavior.
 

Neptune

Member
They call it defensive programming -- in case something ever happens where a value escapes the "test boundary", then you covered your butt with >= / <=
The '==' comparison is really useful for enum values.
Code:
if (val == ENUM_ITEM.staff) {}
Also in other languages (c++ specifically), things like:
Code:
if (val = 5) {}
Would NOT be allowed, and must be:
Code:
if (val == 5) {}
 

TheouAegis

Member
Regarding working with floating points...

When starting with an initial integer value, if you are strictly adding integers (e.g., -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5), then you can use == all the time if the target can actually be reached.

If you start with an integer and are working with fractions which themselves have denominators that are a power of 2 (e.g., 1/4, 5/8, 17/16) then you can use == all the time if the target can actually be reached.

If you are working with fractions which cannot be written with a denominator that is a power of 2, you must use >= or <= (depending on which direction you are operating).

The number 1.5 can be written as 3/2 -- it has a denominator that is a power of 2 (in fact, it is 2). Now, if we were to write that value as a signed 8bit float (GM uses a lot more than that, but the point can be made with just 8), then 3/2 would be written in the computer as 0x0180. The left two digits are the whole value. In our number, the whole value is 1 and you can see that in the 8bit notation. So where does it get the .5? In 8bit notation, 0x80 is half of a full byte. If you divide 0x80 by 0x0100, the value is 1/2. So now let's look at the number again. The computer sees it as 0x0180; if we divide 0x0180 by 0x0100, we get 1.5 as the decimal notation.

All fine and dandy. Now let's look at a bad fraction. Suppose we had 0.2 for our value. It has a 2 in it, but not in the denominator. A value of 0.2 is equal to 1/5. No amount of straight multiplication will yield a power of 2 in the denominator. But why is that an issue? Let's take a look. This time around, we'll work backwards. 0x0100 multiplied by 0.2 is equal to 0x0033. Now let's multiply 0x0033 by 5.

0x0000 + 0x0033 = 0x0033
0x0033 + 0x0033 = 0x0066
0x0066 + 0x0033 = 0x0099
0x0099 + 0x0033 = 0x00CC
0x00CC + 0x0033 = 0x00FF

Oooh! So close! The result is 255/256 -- 1 bit away from a whole number! So what to do? Add it to itself one more time.

0x00FF + 0x0033 = 0x0122

Now the result is over 1, not equal to 1. What further complicates matters here is, assuming the value carried over, the next iteration will complete in 5 steps from there, not 5 steps after 5 steps.

0x0122 + 0x0033 = 0x0155
0x0155 + 0x0033 = 0x0188
0x0188 + 0x0033 = 0x01BB
0x01BB + 0x0033 = 0x01EE
0x01EE + 0x0033 = 0x0211

And then on the next run....

0x0211 + 0x0033 = 0x0244
0x0244 + 0x0033 = 0x0277
0x0277 + 0x0033 = 0x02AA
0x02AA + 0x0033 = 0x02DD
0x02DD + 0x0033 = 0x0300

OH MY GOSH! We have an integer result! This is one of the few times that adding 0.2 to something will ever equal anything (the other time is 1, and that's just because of how GM handles values close to 1). The rest of the time, as you see in the numbers above, you will rarely get an integer result.

But what about comparing a result to another improper fraction? Suppose you add 0.2 to a variable and want to check if it equals 0.4? Or 0.6? Surely that would work, right? Again, let's multiply 0x0100 by the values we're looking for.

0x0100 * 0.4 = 0x0066
0x0100 * 0.6 = 0x0099

So yes, that would work just fine, because each of those numbers is in our results above.

BUT WAIT! There's more! What about comparing to 0.8? To better understand why this isn't reliable, we'll go back to decimal. 0x0100 is equal to 256/256, so when we do 0x0100*0.2, what we're really doing is 256*0.2 in order to get 0x0033. This does not yield a clear result in decimal, though.

256 * 0.2 = 51.2

A value of 51.2 gets rounded down to 51, which is 0x0033. However, what if the value doesn't get rounded down, but rather it gets rounded up?

256 * 0.6 = 153.6
256 * 0.8 = 204.8

So what if those values got rounded up? Well, then their corresponding values in computer terms would be 0x009A and 0x00CD, neither of which are in the set of calculations above. So even though your rate of increase is 0.2 and your target is a direct multiple of your rate of increase, the values have the potential to not be the same once converted into floating point values.

So what is 50*0.2? Here's a hint: it's not 10.


And sometimes rounding algorithms use Gaussian rounding, so even if you increment by an integer value, if the initial value is a fraction it might round up one step and down the next.
 
Last edited:

FrostyCat

Redemption Seeker
If you are working with strictly integers (e.g., -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5), then you can use == all the time.
Even with integers, you still shouldn't use == all the time if the increments are uneven relative to the target value.

Case in point:
For example, if the threshold is at 100 and you check for it using ==, starting at 0 and incrementing by 5 works but not starting at 0 and incrementing by 7. The same threshold checked with >= is fine both ways.
This topic is an example of one of the most important observations in my book for programmers: Novices seek perfection, experts seek mitigation.
 
G

Guest

Guest
@TheouAegis Whoa, thanks! I had to read that a couple times, but that's really interesting and educational. It clarifies some of what I was vaguely sort of aware about how computers work. It also makes sense (at least internally, it's kind of crazy that's how we have to deal with computers' binary fundamentals).

And to think all I associated hex with was editing in-game memory to cheat as a kid.
 

TheouAegis

Member
Even with integers, you still shouldn't use == all the time if the increments are uneven relative to the target value.

Case in point:


This topic is an example of one of the most important observations in my book for programmers: Novices seek perfection, experts seek mitigation.
Yeah, but I consider that the programmer's flaw, not the program's. I mean, if you think 0+1.5n will ever equal 4, that's your fault; round(0+1.5n), sure, but we're not talking about using roundung functions. Although I guess with the core math being taught in schools now, if they don't understand the concept then they might get it in their heads that adding any two numbers will yield a number in between.
 

TheouAegis

Member
@TheouAegis Whoa, thanks! I had to read that a couple times, but that's really interesting and educational. It clarifies some of what I was vaguely sort of aware about how computers work. It also makes sense (at least internally, it's kind of crazy that's how we have to deal with computers' binary fundamentals).

And to think all I associated hex with was editing in-game memory to cheat as a kid.
So what measuring system do you use? Do you measure in inches or in meters? if you measure in inches, you are measuring in binary. You are measuring in HEX. One inch is one nybble. 16 inches is one byte. You can think of it that way. Do you often hear of 0.1 inches? Or even 9/10 of an inch? No, you typically hear of 1/4 inch, 1/2 inch, 1/8 inch, 9/16. Working with floating-point numbers on a computer is no different than trying to fix a European car with an American wrench set. If your car uses metric sized parts and you are working with Imperial sized tools, then you have to accept that you are not using equal sizes.

... I think I'm going to use this analogy from now on. Although it probably wouldn't make sense to Europeans...
 
W

wittynethandle

Guest
Also in other languages (c++ specifically), things like:
Code:
if (val = 5) {}
Would NOT be allowed, and must be:
Code:
if (val == 5) {}
AFAIK, that is allowed in c/c++, but it would be an error since the assignment would always evaluate to true (0 = false, anything else = true).

Try:

Code:
#include <iostream>

int main(void)
{
    int a = 0;
    if(!a)
        std::cout << "False!" << std::endl;
    if(a = 5)
        std::cout << "True!" << std::endl;

    return 0;
}
If you pass -Wall to g++ you will get a warning about suggesting extra parentheses, but you get output of

Code:
False!
True!
because if(a = 5) is equivalent to if(5) which is equivalent to if(true).
 

sp202

Member
If you want to check for a specific image_index, rounding first before == comparation is the way to go.
 
W

wittynethandle

Guest
To me at least, I read "Would NOT be allowed..." as meaning the syntax is invalid and the code won't compile. As in omitting parentheses or semicolons would not be allowed by a c/c++ compiler.
 
O

orange451

Guest
Its because everything is in fact, a float.
If we're being really nit-picky. Every user-created variable in the Game Maker Runner is a double, not a float :x

Regardless everything else you said holds true, and serves as a good explanation for the OPs question.
 
Top