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.

And that's about it.

Ran into that once. It was not fun.

Good blog post!

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