Help Understanding Clamp for Similar Scripts

TLH14

Member
I'm pretty sure I get the basic function behind clamp. If I were to script it out myself, I'd do something along the lines of...
Code:
///clamp(val,min,max)

var val;

if argument[0] < argument[1]
{
    val = argument[1];
}
else if argument[0] > argument[2]
{
    val = argument[2];
}
else val = argument[0];

return(val);
However, that's just the basic logic behind clamp and is not necessarily everything going on in the function; I have no idea how things are running under the hood to make the function perform as it does, that's simply my interpretation. All I've found from searching the internet is answers explaining the basic functionality behind clamp, not anything more specific or advanced.

Why I ask is because I want to create scripts similar to clamp...
Code:
///fold(val,min,max)

var val;

if argument[0] < argument[1]
{
    val = argument[2];
}
else if argument[0] > argument[2]
{
    val = argument[1];
}
else val = argument[0];

return(val);
Code:
///wrap(val,min,max)

var val;

if argument[0] < argument[1]
{
    val = argument[2] - (argument[1] - argument[0]);
}
else if argument[0] > argument[2]
{
    val = argument[1] + (argument[0] - argument[2]);
}
else val = argument[0];

return(val);
Code:
///hem(val,min,max)

var val;

if argument[0] < argument[1]
{
    val = argument[1] + (argument[1] - argument[0]);
}
else if argument[0] > argument[2]
{
    val = argument[2] - (argument[0] - argument[2]);
}
else val = argument[0];

return(val);

These are my interpretations on how to go about the functions I want, but I don't really know if they're the most efficient way to go about things, or if they're missing some key functionality to avoid errors that I didn't think of. Since the nature of these scripts is similar to that of clamp, I was hoping that a more thorough understanding of exactly what clamp does would help me in designing my own scripts.


Also, if there are already established and/or better names for the ideas behind these scripts, do let me know. These are all just what I came up with off the top of my head; I'm new to maths.
 

jo-thijs

Member
There are 2 optimizations for you clamp script (without using clamp itself or something like that):
1) Don't use a temporary variable if not necessary, just return instantly (3 times) instead of saving the result to val and then jump to the return.
2) Don't use argument[X], but use argumentX if the amount of variables your script accepts is a fixed number.

However, any optimizations you could perform on this script would have next to no effect, as they'll be too small.

Under the hood, the actual implementation of clamp depends on the target you're compiling for and possibly the version of GameMaker itself.
It'll probably be nearly exactly the same as your implementation however, only more compiled and secure (as type checks are performed).

Concenring key functionality in your functions,
how much do you care about edge cases?
Do you care what your functions do with strings or arrays passed to them?
Do you care what happens what your scripts do when NaN, +Inf or -Inf are passed to them?
Do you care what happens what your scripts do when undefined is passed to them?
Do you care what happens when argument1 > argument2?
Do you care what happens what happens when argument1 == argument2?
Do you care what happens when argument0 is considered equal to argument1 or argument2,
but is actually a bit different due to precision flaws?

Besides those things, I also wonder.
Don't you want wrap and hem to always return a result between min and max?
They currently don't.

Honestly though, your scripts look just fine, so don't worry to much about it.

Also, if you're new to mathematics, then you might be in for a fun time!
 

TLH14

Member
There are 2 optimizations for you clamp script (without using clamp itself or something like that):
1) Don't use a temporary variable if not necessary, just return instantly (3 times) instead of saving the result to val and then jump to the return.
2) Don't use argument[X], but use argumentX if the amount of variables your script accepts is a fixed number.
Interesting, I'll have to apply this logic to other scripts I've made as well.

There are 2 optimizations for you clamp script (without using clamp itself or something like that):
Concenring key functionality in your functions,
how much do you care about edge cases?
Do you care what your functions do with strings or arrays passed to them?
Do you care what happens what your scripts do when NaN, +Inf or -Inf are passed to them?
Do you care what happens what your scripts do when undefined is passed to them?
Do you care what happens when argument1 > argument2?
Do you care what happens what happens when argument1 == argument2?
Do you care what happens when argument0 is considered equal to argument1 or argument2,
but is actually a bit different due to precision flaws?
In order...
What are they?
Yes.
I think so?
Not so much...
Super yes! Gosh, I should've thought of that.
A little bit.
Yes.

There are 2 optimizations for you clamp script (without using clamp itself or something like that):
Besides those things, I also wonder.
Don't you want wrap and hem to always return a result between min and max?
They currently don't.
Oh my gosh! I totally missed that; yes, I absolutely want things to stay between min and max.
 
A

ariel stempler

Guest
Something like this would probably be a bit more efficient:

if (val < lower){
return(lower);
}
else if (val > upper){
return(upper);
}
return(val);

Also, you could use the "min" and "max" functions to do the same thing:
val = max(lower, min(val, upper));

I don't think it will affect the game's performance too much though :]
 

Yaazarai

Member
You've got the basic idea down for clamp, but there's an easier way to do this using min() and max().
Code:
//clamp(val, min, max)
return min(argument[2], max(argument[0], argument[1]));
This takes the first two arguments val and min and finds the higher value via max(). Once it's done this
it takes the highest of those two values and then finds the lowest between the result of max and max.

So here's what happens if we were to call: clamp(45, 0, 100)
Code:
1.    Which is the Maximum value: 45 or 0?
    MaxResult = max(argument[0], argument[1])
2.    45 is the maximum value.
    MaxResult = 45;
3.     Which is the lowest value: 45 or 100?
    MinResult = min(argument[2], MaxResult);
4.    45 is the lowest value.
    MinResult = 45.
That's the gist of it.


However you can even go further and look into doing the math for min() and max():
Code:
Min(x, y) = y + ((x = (x-y)) & (x >> 31));
Max(x, y) = x - ((y = (x-y)) & (y >> 31));
This does a fun bit...
Let me break this down. The above code for min(x, y) will execute as follows:
Code:
x = (x - y);
R2 = (x >> 31);
R3 = R1 & R2;
R4 = y + R3;
return R4;
First off, R1 will return the difference of x and y even if the result is negative. Next we take that result and get the "sign,"
of the result as R2--the sign is the last bit of an integer, in this case the last bit of a 32-bit integer. We then take R1 and R2
and we perform bitwise-AND (&) on them to reverse the sign of R1 as R3 (If the R1 is negative and we AND (&) it with R2 then
we'll get the positive value of R1 and vice versa for if R1 was negative). We then add R3 to y to get our minimum value.

Example of min(x, y) when the code executes:
Code:
// min(45, 100)
x = (45 - 100);
R1 = x(-55) >> 31;
R2 = x(-55) & R1(-1);
R3 = 100 + R2(-55);
return R3(45);
Voila. The same works for max() with just the change of using x- as the last operation instead of y+.

NOTE: We use "31" in the above code because any 32-bit integer shifted-down 31 bits will give you the sign of the integer.
GameMaker uses 32-bit numbers when performing bit-wise math.
 
Last edited:

jo-thijs

Member
To the suggestion of using min and max, I've got an even better option:
Code:
///clamp(val, min, max)
return clamp(val, min, max);
Using min and max would just raise the same question again: How does GameMaker do those under the hood?
Using if, else, return and comparisons is the closest you'll get to the actual implementation in javascript/C++/assembly (mainly assembly though).

@FatalSleep,
The code you give, together with the explenation as to how min and max work are not really good.

First of all, this line:
Code:
Min(x, y) = y + ((x = (x-y)) & (x >> 31));
I don't know what language that's supposed to be, but it ain't GML.
That line's terrible because the order of operations isn't clear (when does the x = (...) happen?).
You also didn't specify what types x and y are.
To serve GameMaker, they should be double precision floating point numbers, but you seem to treat them as integers.

Then in your explenation, you use this:
Code:
R3 = R1 & R2;
without ever defining R1.

Before that even, you use >> to get the sign of x, which is terrible for 2 reasons:
1) Now you don't only assume x is an integer, but you assume it is a 32-bit integer as well.
2) You're thinking of signed bitshifts (aka arithmetic shifts), but you didn't specify this. Unsigned bitshifts would give you quite some different results.

Now, assuming R1 is actually x, then your reasoning for R1 & R2 inversing the sign of R1 is complete nonsense.
You make a case distinction between R1 being negative and ... R1 being negative.
And you even work out that case wrongly, as a negative number bitwise and a negative number is again a negative number, not a positive number.

In the example you give afterwards, you use completely different names for RN.
The example is otherwise correct.

Now, as for the algorithm itself, it does work (making some assumptions I already pointed out) if you want to take the max of 2 32-bit integers.
It doesn't work for the reasons you explained though, so I'm assuming you just copy pasted that from a site without really knowing how it works.
It isn't easilly extended for floating points or multiple arguments either, as GameMaker requires.
I also don't think the algorithm is any better than just using a simple ternary operator with a single comparison.
Less and cheaper operations are needed there.

@TLH14,
As for wrap, you can implement that as:
Code:
///wrap(val, min, max)

return frac((argument0 - argument1) / (argument2 - argument1)) * (argument2 - argument1) + argument1;
It might not be the most precise code for it and it's definitely not the safest one, but it should work just fine in most cases.
If you really want a precise implementation, I'll take a closer look at it later.

As for hem, you can use the same technique:
Code:
///hem(val, min, max)

return argument2 - abs(frac((argument0 - argument1) / ((argument2 - argument1) * 2)) * ((argument2 - argument1) * 2) + argument1 - argument2);
EDIT:
@FatalSleep, excuse me for missing your comment at the end of your spoiler.
GameMaker converts floating point numbers to signed integers for bitwise operations, but why do you think it is always 32 bit? Is this documented somewhere? I'm actually curious about this.

EDIT:
I actually just tested it and GameMAker doesn't use 32 bit for me, but 64 bit.
This is target/platform and device specific.
 
Last edited:

jo-thijs

Member
Sorry for taking so long to response, but I actually had some trouble creating a more precise implementation.
I recently learned some behaviour of mod though that can help:
Code:
///wrap(val, min, max)

var t = ((argument0 - argument1) mod (argument2 - argument1)) + argument1;
if t < argument1
    return t + (argument2 - argument1);
return t;
Code:
///hem(val, min, max)

var t = ((argument0 - argument1) mod ((argument2 - argument1) * 2)) + argument1;
if t < argument1 * 2 - argument2
    return t + (argument2 - argument1) * 2;
if t < argument1
    return argument1 * 2 - t;
if t > argument2
    return argument2 * 2 - t;
return t;
I haven't tested these codes yet and I'm not sure how precise they actually are
or how precise they are compared to my previous codes, but they should probably be sufficient for whatever you're using.
 

TLH14

Member
Sorry for taking so long to response, but I actually had some trouble creating a more precise implementation.
I recently learned some behaviour of mod though that can help:
Code:
///wrap(val, min, max)

var t = ((argument0 - argument1) mod (argument2 - argument1)) + argument1;
if t < argument1
    return t + (argument2 - argument1);
return t;
Code:
///hem(val, min, max)

var t = ((argument0 - argument1) mod ((argument2 - argument1) * 2)) + argument1;
if t < argument1 * 2 - argument2
    return t + (argument2 - argument1) * 2;
if t < argument1
    return argument1 * 2 - t;
if t > argument2
    return argument2 * 2 - t;
return t;
I haven't tested these codes yet and I'm not sure how precise they actually are
or how precise they are compared to my previous codes, but they should probably be sufficient for whatever you're using.
Unfortunately, there's an error in that these scripts will never return the max of the two values, which I would prefer as that's how clamp works, and it only makes sense these scripts should follow the same logic. Otherwise, wrap works fine; there are multiple problems with hem, but those are all in the logic of how it works, and my original hem script produces the same errors. I'm working on solutions myself, but I thought you'd like to know.

Oh, and a word of encouragement: your scripts are, in fact, far more precise than mine, and actually produce the desired result save for the one error of not returning max; my wrap, for instance, will only work for values that are within the range of less or greater than the difference between min and max.
 
Last edited:

Yaazarai

Member
To the suggestion of using min and max, I've got an even better option:
EDIT:
@FatalSleep, excuse me for missing your comment at the end of your spoiler.
GameMaker converts floating point numbers to signed integers for bitwise operations, but why do you think it is always 32 bit? Is this documented somewhere? I'm actually curious about this.

EDIT:
I actually just tested it and GameMAker doesn't use 32 bit for me, but 64 bit.
This is target/platform and device specific.
The code is just basic cross-language syntax, works in GML and C++, that "Min(...) =" part is just an annoatation saying the following code is for the aforementioned function.
Yes I explained it poorly, but the code works, the example is more just for a mathematical representation of how the min/max functions could work on the backend for integers.
I'm not sure if all shader languages include Min/Max/Clamp, but if not, knowing the math so you can write them yourself is nice, especially because GPUs don't like branching.

I believe the YYG team fixed the 32-bit issue with later versions of GM:Stuio--or you did something wrong and assumed 64-bit.
Regardless--if it was fixed--GameMaker has always done bitwise operations as a conversion to 32-bit signed integers.
You can test this in GM8.1 or earlier versions of GMS.
 

jo-thijs

Member
It doesn't work in GML though. I'm guessing you meant it is pseudocode.

The code doesn't work for floats though, only for 32-bit integers (assuming the correct bitwise shift is applied).

It is an interesting idea though to use these codes for min and max functions for integers in shader languages,
they might actually be more performant there.

I've got the latest non-EA version of GameMaker studio and I'm testing on the windows and windows YYC target.
When I run this:
Code:
var t = 1 << 32;
show_message(t);
I get this as result: 4294967296

When I run this:
Code:
var t = 1 << 62;
show_message(t);
I get this as result: 4611686018427387904

When I run this:
Code:
var t = 1 << 63;
show_message(t);
I get this as result: -9223372036854775808

And when I run this:
Code:
var t = 1 << 64;
show_message(t);
I get this as result: 1

I don't quite think that's possible in 32-bit.

I haven't found anything in the manual about bitwise arithmetics using 32-bit either.
And as far as I can remember, it has always been 64-bit.

Why do you think GameMaker uses 32-bit only?
 
Top