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;
答案 0 :(得分:4)
您的天真标量算法无法提供正确的舍入转换 - 它会在某些输入上遭受双舍入。例如:如果x
为0x88000081
,则转换为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
。