Полное преобразование с одной точностью в половину точности. Это прямая копия из моей версии SSE, поэтому она не имеет отношения к ветви. Он использует тот факт, что в GCC (-true == ~ 0) может быть справедливо и для VisualStudio, но у меня нет копии.
class Float16Compressor
{
union Bits
{
float f;
int32_t si;
uint32_t ui;
};
static int const shift = 13;
static int const shiftSign = 16;
static int32_t const infN = 0x7F800000; // flt32 infinity
static int32_t const maxN = 0x477FE000; // max flt16 normal as a flt32
static int32_t const minN = 0x38800000; // min flt16 normal as a flt32
static int32_t const signN = 0x80000000; // flt32 sign bit
static int32_t const infC = infN >> shift;
static int32_t const nanN = (infC + 1) << shift; // minimum flt16 nan as a flt32
static int32_t const maxC = maxN >> shift;
static int32_t const minC = minN >> shift;
static int32_t const signC = signN >> shiftSign; // flt16 sign bit
static int32_t const mulN = 0x52000000; // (1 << 23)/minN
static int32_t const mulC = 0x33800000; // minN/(1 << (23 - shift))
static int32_t const subC = 0x003FF; // max flt32 subnormal down shifted
static int32_t const norC = 0x00400; // min flt32 normal down shifted
static int32_t const maxD = infC - maxC - 1;
static int32_t const minD = minC - subC - 1;
public:
static uint16_t compress(float value)
{
Bits v, s;
v.f = value;
uint32_t sign = v.si & signN;
v.si ^= sign;
sign >>= shiftSign; // logical shift
s.si = mulN;
s.si = s.f * v.f; // correct subnormals
v.si ^= (s.si^v.si) & -(minN > v.si);
v.si ^= (infN^v.si) & -((infN > v.si) & (v.si > maxN));
v.si ^= (nanN^v.si) & -((nanN > v.si) & (v.si > infN));
v.ui >>= shift; // logical shift
v.si ^= ((v.si - maxD)^v.si) & -(v.si > maxC);
v.si ^= ((v.si - minD)^v.si) & -(v.si > subC);
return v.ui | sign;
}
static float decompress(uint16_t value)
{
Bits v;
v.ui = value;
int32_t sign = v.si & signC;
v.si ^= sign;
sign <<= shiftSign;
v.si ^= ((v.si + minD)^v.si) & -(v.si > subC);
v.si ^= ((v.si + maxD)^v.si) & -(v.si > maxC);
Bits s;
s.si = mulC;
s.f *= v.si;
int32_t mask = -(norC > v.si);
v.si <<= shift;
v.si ^= (s.si^v.si) & mask;
v.si |= sign;
return v.f;
}
};
Так что это много, чтобы принять, но он обрабатывает все субнормальных значения, как бесконечности, тихий Nans, сигнализацию и пренебрежимы малой отрицательный нуль. Конечно, полная поддержка IEEE не всегда необходима. Таким образом, сжатие родовых поплавков:
class FloatCompressor
{
union Bits
{
float f;
int32_t si;
uint32_t ui;
};
bool hasNegatives;
bool noLoss;
int32_t _maxF;
int32_t _minF;
int32_t _epsF;
int32_t _maxC;
int32_t _zeroC;
int32_t _pDelta;
int32_t _nDelta;
int _shift;
static int32_t const signF = 0x80000000;
static int32_t const absF = ~signF;
public:
FloatCompressor(float min, float epsilon, float max, int precision)
{
// legal values
// min <= 0 < epsilon < max
// 0 <= precision <= 23
_shift = 23 - precision;
Bits v;
v.f = min;
_minF = v.si;
v.f = epsilon;
_epsF = v.si;
v.f = max;
_maxF = v.si;
hasNegatives = _minF < 0;
noLoss = _shift == 0;
int32_t pepsU, nepsU;
if(noLoss) {
nepsU = _epsF;
pepsU = _epsF^signF;
_maxC = _maxF^signF;
_zeroC = signF;
} else {
nepsU = uint32_t(_epsF^signF) >> _shift;
pepsU = uint32_t(_epsF) >> _shift;
_maxC = uint32_t(_maxF) >> _shift;
_zeroC = 0;
}
_pDelta = pepsU - _zeroC - 1;
_nDelta = nepsU - _maxC - 1;
}
float clamp(float value)
{
Bits v;
v.f = value;
int32_t max = _maxF;
if(hasNegatives)
max ^= (_minF^_maxF) & -(0 > v.si);
v.si ^= (max^v.si) & -(v.si > max);
v.si &= -(_epsF <= (v.si & absF));
return v.f;
}
uint32_t compress(float value)
{
Bits v;
v.f = clamp(value);
if(noLoss)
v.si ^= signF;
else
v.ui >>= _shift;
if(hasNegatives)
v.si ^= ((v.si - _nDelta)^v.si) & -(v.si > _maxC);
v.si ^= ((v.si - _pDelta)^v.si) & -(v.si > _zeroC);
if(noLoss)
v.si ^= signF;
return v.ui;
}
float decompress(uint32_t value)
{
Bits v;
v.ui = value;
if(noLoss)
v.si ^= signF;
v.si ^= ((v.si + _pDelta)^v.si) & -(v.si > _zeroC);
if(hasNegatives)
v.si ^= ((v.si + _nDelta)^v.si) & -(v.si > _maxC);
if(noLoss)
v.si ^= signF;
else
v.si <<= _shift;
return v.f;
}
};
Это заставляет все значения в допустимом диапазон, нет поддержки NaNs, бесконечности или отрицательного нуля. Epsilon является наименьшим допустимым значением в диапазоне. Прецизионность - это то, сколько бит точности следует сохранить из поплавка. Несмотря на то, что много ветвей выше, они все статичны и будут кэшироваться предиктором ветки в CPU.
Конечно, если ваши значения не требуют логарифмического разрешения, приближающегося к нулю. Тогда линеаризация их в формат фиксированной точки происходит намного быстрее, как уже упоминалось.
Я использую FloatCompressor (версия SSE) в графической библиотеке, чтобы уменьшить размер линейных значений цвета float в памяти.Сжатые поплавки имеют преимущество в создании небольших поисковых таблиц для длительных функций, таких как гамма-коррекция или трансцендентные. Сжатие линейных значений sRGB уменьшается до максимального 12 бит или максимального значения 3011, что отлично подходит для размера таблицы поиска для/из sRGB.
Вы уверены, что вы будете в состоянии измерить эффективность выгоды от этого преобразования? Вам нужно будет отправлять много этих номеров по проводам, чтобы добиться значительной экономии. Вы получаете только 3 десятичные цифры точности, и диапазон не так уж и большой. –
OTOH, CPU по существу свободен в настоящее время, если вы можете потопить свою программу, а преобразование потока ввода-вывода легко нарезается резьбой.Экономия при вводе-выводе будет реальной, если количество отправленных посылок находится где угодно рядом с пропускной способностью сети. То есть это хороший компромисс между пропускной способностью и временем ожидания и, как таковой, имеет значение только тогда, когда у вас действительно есть проблема с bandwitdh и проблемы с задержкой. – MSalters
Есть ли у C++ встроенная поддержка 16-битных поплавков? – Lazer