Remapping one range of values to another, encountering integer overflow

Viewed 235

I need to remap several different ranges of values in my application, for example [0 1.0] to [-1000 1000]. So I created a template class (below) with the previous max/min and type (int/double) and the new max/min and type. The code works for small ranges, but when I try to convert ranges that exceed the integer range I am getting wrong results.

What I mean is if I try and map a range of std::int16_t [-32767 32767] to something else, I am encountering integer overflow/rollover. How can I fix this?

template<class P, class N>
class Remap {
public:
  /**
   * @brief Construct a new Remap object
   * 
   * @param prevMin the previous range minimum value
   * @param prevMax the previous range maximum value
   * @param newMin the new range minimum value
   * @param newMax the new range maximum value
   */
  Remap(P prevMin, P prevMax, N newMin, N newMax) :
    prev_min_(prevMin),
    prev_max_(prevMax),
    new_min_(newMin),
    new_max_(newMax) {
    // TODO: Need to handle if min == max

    prev_range_ = prev_max_ - prev_min_;
    new_range_ = new_max_ - new_min_;
  };

  // Convert a previous value to the new mapping
  N Convert(P v) {
    return static_cast<N>((v - prev_min_) * new_range_ / prev_range_ + new_min_);
  }

private:
  P prev_min_;
  P prev_max_;
  N new_min_;
  N new_max_;
  P prev_range_;
  N new_range_;
};

An example that causes incorrect values

Remap<std::int16_t, double> my_remap(-32767, 32767, -1.0, 1.0);
// Should get 1.0
my_remap.Convert(32767);

I have traced the problem to

prev_range_ = prev_max_ - prev_min;

The correct value should be 65534 however since P is type int16_t I am not getting that value. Suggestions on how I can fix this?

** Edit: ** I can fix this by declaring Remap<std::int32_t, double> ... but not sure if there is a better/proper solution that actually uses the desired type of std::int16_t.

1 Answers

One thing that could mitigate this problem would be take out prev_range_ and new_range_ entirely and replace them with a scale factor, which is inherently a floating point number.

#include <stdexcept>      // std::out_of_range

template<class P, class N>
class Remap {
public:
  /**
   * @brief Construct a new Remap object
   * 
   * @param prevMin the previous range minimum value
   * @param prevMax the previous range maximum value
   * @param newMin the new range minimum value
   * @param newMax the new range maximum value
   */
  Remap(P prevMin, P prevMax, N newMin, N newMax) :
    prev_min_(prevMin),
    prev_max_(prevMax),
    new_min_(newMin),
    new_max_(newMax)
  {
    if (prev_min_ == prev_max_)
        throw std::out_of_range("prev_min_ == prev_max_ !!\n");
    scale_ = (0.5*new_max_ - 0.5*new_min_)/(0.5*prev_max_ - 0.5*prev_min_);
  }

  // Convert a previous value to the new mapping
  N Convert(P v)
  {
    return 2*static_cast<N>((0.5*v - 0.5*prev_min_) * scale_ + 0.5*new_min_);
  }

private:
  P prev_min_;
  P prev_max_;
  N new_min_;
  N new_max_;
  double scale_; // Replaces prev_range_ and new_range_
};
Related