如何将小数标准化为一个范围内的数值

问题描述

如何将范围 [0.0,1.0] 内的浮点小数标准化为一个介于最大值和最小值范围之间的数字?规范化是正确的词吗?做我想做的事。

如果我输入 0.5 并且范围是 010,那么输出应该是 5

如果我输入 0.799999 并且范围是 010,那么输出应该是 8

如果我输入 0.345 并且范围是 010,那么输出应该是 3

如果我输入 0.555 并且范围是 020,那么输出应该是 11

unsigned int normalise(float value,unsigned int min,unsigned int max) {
    // Return value normalised between min and max
}

我不太确定 normalise 在算术上下文中是否是正确的词。

解决方法

您要查找的术语是“插值”,也就是“线性插值”,也就是 lerp

您可以轻松创建一个模板来满足您的需求:

template<typename T>
[[nodiscard]] T lerp(const T& a,const T& b,float t) {
    return ((1.0f - t) * a) + (t * b);
}
,

您的问题带有 c++11 标记,但值得一提的是,如果您在 c++20 中进行编译,std::lerp() 可以帮助您解决问题。

提供您自己的 lerp() 相当简单。这是@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,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);
}


但是,lerp() 并不能完全满足您的需求。

如果我输入 0.799999 并且范围是 0 到 10,那么输出应该是 8。

您最终会得到 7,因为默认情况下 C++ 会将所有内容都四舍五入到 0。因此,您还必须手动将该值四舍五入为最接近的整数。您可以将其作为 lerp 的一部分来执行,但是 lerp() 具有相当明确的预期行为。搞砸了可能会带来惊喜。

最好为此创建一个单独的方法,在后台使用 lerp()

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,const Tt& t) {
  static_assert(std::is_integral<IntT>::value,"IntT must be an integer type");
  return std::round(lerp(a,t));
}

请注意,这会强制 ab 和返回类型都为相同类型。这是一个有点武断的决定,您可能希望也可能不想根据自己的需要进行更改。

用法:

int x = interpolateToNearest(0,10,0.5);