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