# GameMaker: smart floor/ceil functions Alternatively best described as "how can floor(4) be 3 and how to fix it".

## The problem

Let's suppose you are incrementing a value by 0.05 in iterations or over time.

```var val = 0;
repeat (80) val += 0.05;
show_debug_message(val);
show_debug_message(floor(val));
show_debug_message(ceil(val));
```

Contrary expectations, the output is 4.00, 3, and 4.

This is the result of a round-off error - 0.05 cannot be precisely represented in floating-point, and thus adding 0.05 to a value 80 times doesn't quite add up to 4. This can be tested using

```show_debug_message(string_format(val, 0, 15));
```

which shows that val is, in fact, 3.999999999999994.

## Solutions

### Smarter floor/ceil functions

First, let's take a look at the code:

```/// smart_floor(value)
/// @param value
var v = argument0;
if (sign(v) < 0) {
if (frac(v) == 0) return ceil(v);
} else {
if (frac(v) == 1) return ceil(v);
}
return floor(v);
```
```/// smart_ceil(value)
/// @param value
var v = argument0;
if (sign(v) < 0) {
if (frac(v) == -1) return floor(v);
} else {
if (frac(v) == 0) return floor(v);
}
return ceil(v);
```

Evidently, it works, but what does it mean?

The way this works is that GameMaker implements epsilon - when you do (a == b), what really happens is (a >= b - eps && a <= b + eps). This allows us to do a seemingly nonsensical frac(v) == 1 comparison that would really mean v > 1 - eps. sign(v) is used for the same reason - v < 0 alone would be false for values that are negative but are within epsilon-range.

If we wanted to do this in a language that does not have epsilon built-in - say, JavaScript, we'd do everything ourselves:

```var eps = 1/1000000;
function smartFloor(v) {
if (v < 0) {
if (v % 1 > -eps) return Math.ceil(v);
} else if (v % 1 > 1-eps) return Math.ceil(v);
return Math.floor(v);
}
function smartCeil(v) {
if (v < 0) {
if (v % 1 < eps-1) return Math.floor(v);
} else if (v % 1 < eps) return Math.floor(v);
return Math.ceil(v);
}
```

### round()

Depending on your circumstances, you may be able to get away with using round to snap the value to the nearest integer - since the function uses bankers' rounding, it's not going to fail at X±eps, although you should watch out for values close to .5 instead.

### Avoiding round-off errors

The best way to avoid round-off errors is not to store your increment-able values as floating-point more often than you should. So, for example, with the initial sample, if you were to add an integer and then multiply by step,

```var counter = 0;
for (var i = 0; i < 80; i++) counter++;
var val = counter * 0.05;
```

this would work as intended:

```show_debug_message(val); // 4
show_debug_message(floor(val)); // 4
show_debug_message(ceil(val)); // 4
```

even for least-usual values like dividing by 3.

## 4 thoughts on “GameMaker: smart floor/ceil functions”

1. Dan on said:

Thanks for this. I found this several months ago and it fixed my issue. However, another similar issue came up where this script failed. Using smart_floor, this below:

“`
frac(.999999999999773) == 1
“`

Ends up equaling 1, therefore ceiling the value instead of flooring. I read up a bit more and change epsilon in game maker to a smaller value, which made this work. But then, if I am changing it to a smaller value, why not use the floor / ceil functions built in to Game Maker? Also, are you aware of any consequences using really small values of epsilon?

• Vadim on said:

The precise purpose of smart_floor() is to return 1 for values sufficiently close to 1. Else you’d be using “regular” floor().

2. SSS on said:

Ran into that once. It was not fun.
Good blog post!

3. Max Oakland on said:

This is destroying my brain. Luckily I haven’t run into the problem yet

This site uses Akismet to reduce spam. Learn how your comment data is processed.