Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert floating-point value to cyclic range?

I'm not sure if I'm using the right terminology, but occasionally I find myself needing to canonicalize a floating-point value to a range in a cyclic manner. (This can be useful, for instance, for values that represent rotations, to special-case rotations by whole quadrants or eliminate redundant rotations by whole turns.)

For example, assuming a cycle range of 1.0, the result should be: 0 <= result < 1.0.
Inputs 0.25, 1.25, 2.25 and so on should all become 0.25.

Of course, this is what the modulo operator % supposedly does:

System.out.println(1.25 % 1); // 0.25

But it can still produce a negative result for negative input:

System.out.println(-1.75 % 1); // -0.75, rather than 0.25 as wanted

So for years I've been writing this pattern whenever I needed this functionality:

x %= range;
if (x < 0) x += range;

Today I discovered a bug: starting from very small negative inputs (e.g., x = -1e-20), it turns out that the floating-point result of x += range rounds up to exactly range, so, out of range.

Which has led to this:

static double cyclic(double x, double range) {
    x %= range;
    if (x < 0) {
        x += range;
        if (x >= range) x = 0;
    }
    return x;
}

Even though each line has a justification for being there, the whole is grotesque. (And it comes in two flavors, one for float and one for double, for double the grotesquery.)

I would like to know:

  • Is my "cyclic" function above finally robust against all floating-point oddities?

  • Is there a nicer solution? This doesn't feel like it should be so complex. I feel like I'm missing a trick.

like image 660
Boann Avatar asked Jun 01 '26 00:06

Boann


1 Answers

The known workaround is to use a modulo that round the quotient to nearest integer, instead of truncating the quotient.

The result is a modulo in interval (-range/2,range/2] or [-range/2,range/2) depending on the parity of the quotient (odd or even).

This is the purpose of IEEE754 remainder function, std::remainder in C++, IEEEremainder in java (https://www.w3schools.com/java/ref_math_ieeeremainder.asp) function name may vary in other languages/math libraries

A correct implementation of such remainder function shall be exact (not subject to rounding errors).

like image 54
aka.nice Avatar answered Jun 03 '26 12:06

aka.nice



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!