Мне нужно преобразовать набор чисел из одного диапазона в другой, сохраняя относительное распределение значений.Изменение шкалы диапазона значений произвольных числовых типов
Например, вектор, содержащий произвольно сгенерированные поплавки, можно масштабировать, чтобы поместиться в возможные значения unsigned char (0..255). Игнорируя преобразование типа, это означало бы, что любой вход был предоставлен (например, от -1.0 до 1.0), все числа будут масштабированы до 0.0 до 255.0 (или около того).
Я создал класс шаблона для выполнения этого преобразования, который может быть применен к коллекции с помощью std::transform
:
template <class TYPE>
class scale_value {
const TYPE fmin, tmin, ratio;
public:
TYPE operator()(const TYPE& v) {
TYPE vv(v);
vv += (TYPE(0) - fmin); // offset according to input minimum
vv *= ratio; // apply the scaling factor
vv -= (TYPE(0) - tmin); // offset according to output minimum
return vv;
}
// constructor takes input min,max and output min,max
scale_value(const TYPE& pfmin, const TYPE& pfmax, const TYPE& ptmin, const TYPE& ptmax)
: fmin(pfmin), tmin(ptmin), ratio((ptmax-tmin)/(pfmax-fmin)) { }
// some code removed for brevity
};
Однако приведенный выше код работает корректно только для действительных чисел (float
, double
, .. .). Целые работать при масштабировании до, но даже тогда только по целые коэффициенты:
float scale_test_float[] = {0.0, 0.5, 1.0, 1.5, 2.0};
int scale_test_int[] = {0, 5, 10, 15, 20};
// create up-scalers
scale_value<float> scale_up_float(0.0, 2.0, 100.0, 200.0);
scale_value<int> scale_up_int(0, 20, 100, 200);
// create down-scalers
scale_value<float> scale_down_float(100.0, 200.0, 0.0, 2.0);
scale_value<int> scale_down_int(100, 200, 0, 20);
std::transform(scale_test_float, scale_test_float+5, scale_test_float, scale_up_float);
// scale_test_float -> 100.0, 125.0, 150.0, 175.0, 200.0
std::transform(scale_test_int, scale_test_int+5, scale_test_int, scale_up_int);
// scale_test_int -> 100, 125, 150, 175, 200
std::transform(scale_test_float, scale_test_float+5, scale_test_float, scale_down_float);
// scale_test_float -> 0.0, 0.5, 1.0, 1.5, 2.0
std::transform(scale_test_int, scale_test_int+5, scale_test_int, scale_down_int);
// scale_test_int -> 0, 0, 0, 0, 0 : fails due to ratio being rounded to 0
Мое текущее решение этого вопроса хранить все внутренние для scale_value
как double
и использовать преобразование типов при необходимости:
TYPE operator()(const TYPE& v) {
double vv(static_cast<double>(v));
vv += (0.0 - fmin); // offset according to input minimum
vv *= ratio; // apply the scaling factor
vv -= (0.0 - tmin); // offset according to output minimum
return static_cast<TYPE>(vv);
}
Это работает в большинстве случаев, хотя и с некоторыми ошибками с целыми числами, поскольку значения усекаются, а не округлены. Например, масштабирование {0,5,10,15,20}
от 0..20
до 20..35
, а затем назад дает {0,4,9,14,20}
.
Итак, мой вопрос в том, есть ли лучший способ сделать это? В случае масштабирования коллекции float
s преобразования типов кажутся довольно избыточными, тогда как при масштабировании int
s возникают ошибки из-за усечения.
В стороне, я был удивлен, не заметив что-то (по крайней мере, ничего очевидного) в boost для этой цели. Возможно, я пропустил это - разные библиотеки математики меня путают.
Edit: Я понимаю, что я мог бы специализироваться operator()
для конкретных типов, но это означало бы много кода дублирования, который побеждает один из полезных частей шаблонов. Если не существует способа, скажем, специализироваться один раз для всех неплавающих типов (short, int, uint, ...).
Что вы хотите, чтобы ваш код делать, если, например, вы пытаетесь масштабировать целочисленный диапазон '[1, 3]' в целочисленный диапазон '[1, 2]'? Должен ли источник 2s масштабироваться до '1',' 2' или 50/50 разделить между двумя значениями результата? –
Я ... не определился :) В моих целях это не имеет большого значения, что происходит - я думаю, что добавление в разделение 50/50 добавило бы много сложности, требуя отслеживания предыдущих значений. – icabod
Спасибо за этот замечательный фрагмент кода. Я всегда выполнял масштабирование диапазона «вручную», всегда отлаживая много глупых ошибок, это избавит меня от многих головных болей. – Avio