将uint32的向量转换为float向量的最有效方法是什么?

时间:2012-02-05 18:18:00

标签: assembly floating-point x86 sse

x86没有SSE指令从 unsigned int32转换为浮点数。实现这一目标的最有效指令序列是什么?

编辑: 为了澄清,我想做以下标量操作的向量序列:

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 个答案:

答案 0 :(得分:4)

您的天真标量算法无法提供正确的舍入转换 - 它会在某些输入上遭受双舍入。例如:如果x0x88000081,则转换为float的正确舍入结果为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位宽,所以转换为float不会产生任何舍入。仅在添加两半时才进行舍入;因为add是一个正确舍入的操作,所以整个转换都是正确舍入的。

相比之下,你的天真实现首先将低31位转换为浮点数,这会导致舍入,然后有条件地将2 ^ 31加到该结果,这可能会导致第二次舍入。每当你在转换中有两个单独的舍入点时,除非你非常小心它们是如何发生的,否则你不应该期望结果被正确舍入。

答案 1 :(得分:1)

这是基于旧的但有用的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);
}

答案 2 :(得分:1)

当您询问时,这不可用,但AVX512F添加了vcvtudq2ps