Reputation: 8259
How can I normalise a floating point decimal in the range [0.0, 1.0] to become a number that is set between a range of max and min value? Is normalise the right word? Following what I want to do.
If I input 0.5
and the range is 0
to 10
, then the output should be 5
.
If I input 0.799999
and the range is 0
to 10
, then the output should be 8
.
If I input 0.345
and the range is 0
to 10
, then the output should be 3
.
If I input 0.555
and the range is 0
to 20
, then the output should be 11
.
unsigned int Normalise(float value, unsigned int min, unsigned int max) {
// Return value normalised between min and max
}
I am not super sure if normalise is the right word for this in arithmetic context.
Upvotes: 2
Views: 827
Reputation:
Your question has the c++11
tag, but it's worth mentioning that std::lerp()
gets you halfway there if you are compiling in c++20.
Providing your own lerp()
is reasonably simple. Here's a more complete implementation of the function proposed by @Casey:
#include <cassert>
#include <type_traits>
template<typename Ta, typename Tb, typename Tt>
constexpr auto lerp(const Ta& a, const Tb& b, const Tt& t) {
static_assert(std::is_floating_point_v<Tt>);
assert(t >= Tt{0} && t <= Tt{1});
return a + t * (b - a);
}
// c++11 version:
template<typename Ta, typename Tb, typename Tt>
constexpr auto lerp(const Ta& a, const Tb& b, const Tt& t) -> decltype(a + t * (b - a)) {
static_assert(std::is_floating_point<Tt>::value, "Tt must be a floating point type");
// assert(t >= Tt{0} && t <= Tt{1}); //can't assert in constexpr code in C++11 :(
return a + t * (b - a);
}
However, lerp()
doesn't quite get you where you want.
If I input 0.799999 and the range is 0 to 10, then the output should be 8.
You'd end up with a 7 since C++ rounds everything towards 0 by default. So you'll also have to manually round the value to the nearest integer. You could do this as part of the lerp, but lerp()
has a fairly well defined expected behavior. Messing with that could lead to surprises.
It's better to create a seperate method for this that makes use of lerp()
under the hood:
template<typename IntT, typename Tt>
constexpr IntT interpolateToNearest(const IntT& a, const IntT& b, const Tt& t) {
static_assert(std::is_integral_v<IntT>);
// There's a hidden implicit cast to IntT here.
return std::round(lerp(a, b, t));
}
// c++11 version:
template<typename IntT, typename Tt>
constexpr IntT interpolateToNearest(const IntT& a, const IntT& b, const Tt& t) {
static_assert(std::is_integral<IntT>::value, "IntT must be an integer type");
return std::round(lerp(a, b, t));
}
Note that this enforces that a
, b
, and the return type all be the same type. That's a bit of an arbitrary decision, and something you may or may not want to change based on your needs.
usage:
int x = interpolateToNearest(0, 10, 0.5);
Upvotes: 1
Reputation: 10946
The term you are looking for is "Interpolation" a.k.a "Linear Interpolation" a.k.a lerp
:
You can easily create a template that will do what you want:
template<typename T>
[[nodiscard]] T lerp(const T& a, const T& b, float t) {
return ((1.0f - t) * a) + (t * b);
}
Upvotes: 3