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

### Related posts:

| | 2 Responses You can also find me on Twitter or Tumblr if you'd like.

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

1. SSS on said:

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

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