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