Самый эффективный способ конвертировать вектор из uint32 в вектор с плавающей точкой? - PullRequest
5 голосов
/ 05 февраля 2012

x86 не имеет инструкции SSE для преобразования из unsigned int32 в число с плавающей запятой. Какая последовательность действий была бы наиболее эффективной для достижения этой цели?

EDIT: Чтобы уточнить, я хочу сделать векторную последовательность следующей скалярной операции:

unsigned int x = ...
float res = (float)x;

EDIT2: вот простой алгоритм скалярного преобразования.

unsigned int x = ...
float bias = 0.f;
if (x > 0x7fffffff) {
    bias = (float)0x80000000;
    x -= 0x80000000;
}
res = signed_convert(x) + bias;

Ответы [ 3 ]

4 голосов
/ 06 февраля 2012

Ваш наивный скалярный алгоритм не обеспечивает правильно округленное преобразование - он будет страдать от двойного округления на некоторых входах. Например: если x равно 0x88000081, то правильно округленный результат преобразования в число с плавающей запятой равен 2281701632.0f, но вместо этого ваш скалярный алгоритм вернет 2281701376.0f.

Вне моей головы, вы можете сделать правильное преобразование следующим образом (как я уже сказал, это не в моей голове, поэтому, вероятно, можно где-то сохранить инструкцию):

movdqa   xmm1,  xmm0    // make a copy of x
psrld    xmm0,  16      // high 16 bits of x
pand     xmm1, [mask]   // low 16 bits of x
orps     xmm0, [onep39] // float(2^39 + high 16 bits of x)
cvtdq2ps xmm1, xmm1     // float(low 16 bits of x)
subps    xmm0, [onep39] // float(high 16 bits of x)
addps    xmm0,  xmm1    // float(x)

где константы имеют следующие значения:

mask:   0000ffff 0000ffff 0000ffff 0000ffff
onep39: 53000000 53000000 53000000 53000000

Для этого нужно отдельно преобразовать верхнюю и нижнюю половины каждой полосы в число с плавающей запятой, а затем сложить эти преобразованные значения вместе. Поскольку каждая половина имеет ширину всего 16 бит, преобразование в число с плавающей запятой не требует округления. Округление происходит только при добавлении двух половинок; поскольку сложение является правильно округленной операцией, все преобразования правильно округляются.

Напротив, ваша наивная реализация сначала конвертирует младшие 31 бит в плавающее число, что вызывает округление, затем условно добавляет 2 ^ 31 к этому результату, что может вызвать второе округление. Каждый раз, когда у вас есть две отдельные точки округления в конверсии, если вы не слишком осторожны в отношении того, как они происходят, вы не должны ожидать, что результат будет правильно округлен.

1 голос
/ 15 марта 2018

Это было недоступно, когда вы спросили, но AVX512F добавил vcvtudq2ps.

1 голос
/ 06 февраля 2012

Это основано на примере старой, но полезной документации по миграции Apple AltiVec-SSE, которая, к сожалению, больше не доступна в http://developer.apple.com:

inline __m128 _mm_ctf_epu32(const __m128i v)
{
    const __m128 two16 = _mm_set1_ps(0x1.0p16f);

    // Avoid double rounding by doing two exact conversions
    // of high and low 16-bit segments
    const __m128i hi = _mm_srli_epi32((__m128i)v, 16);
    const __m128i lo = _mm_srli_epi32(_mm_slli_epi32((__m128i)v, 16), 16);
    const __m128 fHi = _mm_mul_ps(_mm_cvtepi32_ps(hi), two16);
    const __m128 fLo = _mm_cvtepi32_ps(lo);

    // do single rounding according to current rounding mode
    return _mm_add_ps(fHi, fLo);
}
...