Turn a float into a value between 0 and 1 (inclusive)?

Biotronic simen.kjaras at gmail.com
Tue Nov 21 11:59:59 UTC 2017


On Tuesday, 21 November 2017 at 09:21:29 UTC, Chirs Forest wrote:
> I'm interpolating some values and I need to make an 
> (elapsed_time/duration) value a float between 0 and 1 
> (inclusive of 0 and 1). The elapsed_time might be more than the 
> duration, and in some cases might be 0 or less. What's the most 
> efficient way to cap out of bounds values to 0 and 1? I can do 
> a check and cap them manually, but if I'm doing a lot of these 
> operations I'd like to pick the least resource intensive way.

Good old comparisons should be plenty in this case:

T clamp(T)(T value, T min, T max) {
     return value < min ? min : value > max ? max : value;
}

That's two comparisons and two conditional moves. You're not 
gonna beat that.

> Also, if I wanted out of bounds values to wrap (2.5 becomes 
> 0.5) how would I get that value and also have 1.0 not give me 
> 0.0?

As Petar points out, this is somewhat problematic. You think of 
the codomain (set of possible results) as being the size (1.0 - 
0.0) = 1.0. However, since you include 1.0 in the set, the size 
is actually 1.0 + ε, which is very slightly larger. You could use 
a slightly modified version of Petar's function:

T zeroToOne(T)(T val) { return val % (1+T.epsilon); }

However, this induces rounding errors on larger numbers. e.g. 1.0 
+ ε becomes 0, while you'd want it to be ε.

I guess I should ask for some clarification - what would you 
expect the return value to be for 2.0? Is that 0.0 or 1.0? How 
about for -1.0?

A simple function that does give the results you want between 0.0 
and 1.0 (inclusive) is this:

T zeroToOne(T)(T val) {
     if (val >= 0 && val <= 1.0) {
         return val;
     }
     return val % 1;
}

The problem now, however, is that zeroToOne gives negative 
results for negative values. This is probably not what you want. 
This can be fixed with std.math.signbit:

T zeroToOne(T)(T val) {
     import std.math : signbit;
     if (val >= 0 && val <= 1.0) {
         return val;
     }
     return (val % 1) + val.signbit;
}

If you're willing to give up the last 1/8388608th of precision 
(for float, 1/4503599627370496th for double) that including 1.0 
gives you, this can become:

T zeroToOne(T)(T val) {
     import std.math : signbit;
     return (val % 1) + val.signbit;
}

Which is easier to read, and consistent for values outside the 
[0,1) interval.

--
   Biotronic


More information about the Digitalmars-d-learn mailing list