TheWaterProgrammer
TheWaterProgrammer

Reputation: 8259

How to normalise a decimal into a number that is a value within a range

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

Answers (2)

user4442671
user4442671

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

Casey
Casey

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

Related Questions