views:

48

answers:

2

I have following code:

template<typename I,typename O> O convertRatio(I input,
    I inpMinLevel = std::numeric_limits<I>::min(),
    I inpMaxLevel = std::numeric_limits<I>::max(),
    O outMinLevel = std::numeric_limits<O>::min(),
    O outMaxLevel = std::numeric_limits<O>::max() )
{
    double inpRange = abs(double(inpMaxLevel - inpMinLevel));
    double outRange = abs(double(outMaxLevel - outMinLevel));
    double level    = double(input)/inpRange;
    return O(outRange*level);
}

the usage is something like this:

 int value = convertRatio<float,int,-1.0f,1.0f>(0.5); 
 //value is around 1073741823 ( a quarter range of signed int)

the problem is for I=int and O=float with function default parameter:

 float value = convertRatio<int,float>(123456); 

the line double(inpMaxLevel - inpMinLevel) result is -1.0, and I expect it to be 4294967295 in float.

do you have any idea to do it better? the base idea is just to convert a value from a range to another range with posibility of different data type.

A: 

Try

(double) inpMaxLevel - (double) inpMinLevel

instead. What you are doing currently is subtracting max from min while the numbers are still of type int - which necessarily overflows; a signed int is fundamentally incapable of representing the difference between its min and max.

romkyns
do you have any idea to solve that rounding or overflow?
uray
@uray - on a closer look, the overflow won't occur because, to the best of my knowledge, no other numeric type can overflow a double. As for rounding errors - I'm not sure, sorry.
romkyns
A: 

Adding to romkyns answer, besides casting all values to doubles before casting to prevent overflows, your code returns wrong results when the lower bounds are distinct than 0, because you don't adjust the values appropiately. The idea is mapping the range [in_min, in_max] to the range [out_min, out_max], so:

  • f(in_min) = out_min
  • f(in_max) = out_max

Let x be the value to map. The algorithm is something like:

  • Map the range [in_min, in_max] to [0, in_max - in_min]. To do this, substract in_min from x.
  • Map the range [0, in_max - in_min] to [0, 1]. To do this, divide x by (in_max - in_min).
  • Map the range [0, 1] to [0, out_max - out_min]. To do this, multiply x by (out_max - out_min).
  • Map the range [0, out_max - out_min] to [out_min, out_max]. To do this, add out_min to x.

The following implementation in C++ does this (I will forget the default values to make the code clearer:

template <class I, class O>
O convertRatio(I x, I in_min, I in_max, O out_min, O out_max) {
  const double t = ((double)x - (double)in_min) /
                   ((double)in_max - (double)in_min);
  const double res = t * ((double)out_max - (double)out_min) + out_min;
  return O(res);
}

Notice that I didn't took the absolute value of the range sizes. This allows reverse mapping. For example, it makes possible to map [-1.0, 1.0] to [3.0, 2.0], giving the following results:

  • convertRatio(-1.0, -1.0, 1.0, 3.0, 2.0) = 3.0
  • convertRatio(-0.8, -1.0, 1.0, 3.0, 2.0) = 2.9
  • convertRatio(0.8, -1.0, 1.0, 3.0, 2.0) = 2.1
  • convertRatio(1.0, -1.0, 1.0, 3.0, 2.0) = 2.0

The only condition needed is that in_min != in_max (to prevent division by zero) and out_min != out_max (otherwise, all inputs will be mapped to the same point). To prevent rounding errors, try to not use small ranges.

jbernadas