x86 does not have an SSE instruction to convert from unsigned int32 to floating point. What would be the most efficient instruction sequence for achieving this?
EDIT:
To clarify, i want to do the vector sequence of the following scalar operation:
unsigned int x = ...
float res = (float)x;
EDIT2: Here is a naive algorithm for doing a scalar conversion.
unsigned int x = ...
float bias = 0.f;
if (x > 0x7fffffff) {
bias = (float)0x80000000;
x -= 0x80000000;
}
res = signed_convert(x) + bias;
Your naive scalar algorithm doesn't deliver a correctly-rounded conversion -- it will suffer from double rounding on certain inputs. As an example: if x
is 0x88000081
, then the correctly-rounded result of conversion to float is 2281701632.0f
, but your scalar algorithm will return 2281701376.0f
instead.
Off the top of my head, you can do a correct conversion as follows (as I said, this is off the top of my head, so it's likely possible to save an instruction somewhere):
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)
where the constants have the following values:
mask: 0000ffff 0000ffff 0000ffff 0000ffff
onep39: 53000000 53000000 53000000 53000000
What this does is separately convert the high- and low-halves of each lane to floating-point, then add these converted values together. Because each half is only 16 bits wide, the conversion to float does not incur any rounding. Rounding only occurs when the two halves are added; because addition is a correctly-rounded operation, the entire conversion is correctly rounded.
By contrast, your naive implementation first converts the low 31 bits to float, which incurs a rounding, then conditionally adds 2^31 to that result, which may cause a second rounding. Any time you have two separate rounding points in a conversion, unless you are exceedingly careful about how they occur, you should not expect the result to be correctly rounded.
This is based on an example from the old but useful Apple AltiVec-SSE migration documentation which unfortunately is now no longer available at 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);
}
This wasn't available when you asked, but AVX512F added vcvtudq2ps
.