Search code examples
c++floating-pointfloating-accuracy

How do you properly "snap" to a value?


Let say I've set a step of 0.1 in my application. So, whatever fp value I get, I just need 1 digit after the comma.

So, 47.93434 must be 47.9 (or at least, the nearest fp representable value).

If I write this:

double value = 47.9;

It correctly "snap" to the nearest fp value it can get, which is:

47.89999999999999857891452847979962825775146484375 // fp value
101111.11100110011001100110011001100110011001100110011 // binary

Now, suppose "I don't write those values", but I got them from a software. And than I need to snap it. I wrote this function:

inline double SnapValue(double value, double step) {
    return round(value / step) * step;
}

But it returns these values:

47.900000000000005684341886080801486968994140625 // fp value
101111.11100110011001100110011001100110011001100110100 // binary

which is formally a little far than the first example (its the "next" fp value, 011 + 1).

How would you get the first value (which is "more correct") for each input value?

Here's the testing code.

NOTE: the step can be different - i.e. step = 0.25 need to snap value around the nearest 0.25 X. Example: a step of 0.25 will return values as 0, 0.25, 0.50, 0.75, 1.0, 1.25 and so on. Thus, given an input of 1.30, it need to wrap to the nearest snapped value - i.e. 1.25.


Solution

  • You could try to use rational values instead of floating point. The latter are often inaccurate already, so not really an ideal match for a step.

    inline double snap(double original, int numerator, int denominator)
    {
        return round(original * denominator / numerator) * numerator / denominator;
    }
    

    Say you want steps of 0.4, then use 2 / 5:

    snap(1.7435, 2, 5) = round(4.35875) * 2 / 5 = 4 * 2 / 5 = 1.6 (or what comes closest to it)