当前位置:  开发笔记 > 编程语言 > 正文

如何使用SSE执行uint32/float转换?

如何解决《如何使用SSE执行uint32/float转换?》经验,为你挑选了2个好方法。

在SSE中有一个函数_mm_cvtepi32_ps(__m128i input),它接受32位宽的有符号整数(int32_t)的输入向量并将它们转换为floats.

现在,我想将输入整数解释为未签名.但是没有功能_mm_cvtepu32_ps,我找不到一个实现.你知道我在哪里可以找到这样的功能,或者至少对实现有所暗示吗?为了说明结果的差异:

unsigned int a = 2480160505; // 10010011 11010100 00111110 11111001   
float a1 = a; // 01001111 00010011 11010100 00111111;  
float a2 = (signed int)a; // 11001110 11011000 01010111 10000010

wim.. 6

使用Paul R的解决方案和我以前的解决方案,舍入的浮点数与原始整数之间的差小于或等于0.75 ULP(最后一位的单位)。在这些方法中,可能会在两个地方出现舍入:_mm_cvtepi32_ps和_mm_add_ps。这导致某些输入的结果不太准确。

例如,使用Paul R的方法,将0x2000003 = 33554435转换为33554432.0,但33554436.0也作为浮点数存在,这在这里会更好。我以前的解决方案也存在类似的错误。编译器生成的代码也可能发生这种不准确的结果,请参见此处。

遵循gcc的方法 (请参阅Peter Cordes对另一个SO问题的回答),可以在0.5 ULP内获得准确的转换:

inline __m128 _mm_cvtepu32_ps(const __m128i v)
{
    __m128i msk_lo    = _mm_set1_epi32(0xFFFF);
    __m128  cnst65536f= _mm_set1_ps(65536.0f);

    __m128i v_lo      = _mm_and_si128(v,msk_lo);          /* extract the 16 lowest significant bits of v                                   */
    __m128i v_hi      = _mm_srli_epi32(v,16);             /* 16 most significant bits of v                                                 */
    __m128  v_lo_flt  = _mm_cvtepi32_ps(v_lo);            /* No rounding                                                                   */
    __m128  v_hi_flt  = _mm_cvtepi32_ps(v_hi);            /* No rounding                                                                   */
            v_hi_flt  = _mm_mul_ps(cnst65536f,v_hi_flt);  /* No rounding                                                                   */
    return              _mm_add_ps(v_hi_flt,v_lo_flt);    /* Rounding may occur here, mul and add may fuse to fma for haswell and newer    */
}                                                         /* _mm_add_ps is guaranteed to give results with an error of at most 0.5 ULP     */

请注意,只要_mm_cvt_ps可以将两个片段都转换为浮点数而无需取整,则可以使用其他高位/低位分区。例如,具有20个高位和12个低位的分区将同样有效。



1> wim..:

使用Paul R的解决方案和我以前的解决方案,舍入的浮点数与原始整数之间的差小于或等于0.75 ULP(最后一位的单位)。在这些方法中,可能会在两个地方出现舍入:_mm_cvtepi32_ps和_mm_add_ps。这导致某些输入的结果不太准确。

例如,使用Paul R的方法,将0x2000003 = 33554435转换为33554432.0,但33554436.0也作为浮点数存在,这在这里会更好。我以前的解决方案也存在类似的错误。编译器生成的代码也可能发生这种不准确的结果,请参见此处。

遵循gcc的方法 (请参阅Peter Cordes对另一个SO问题的回答),可以在0.5 ULP内获得准确的转换:

inline __m128 _mm_cvtepu32_ps(const __m128i v)
{
    __m128i msk_lo    = _mm_set1_epi32(0xFFFF);
    __m128  cnst65536f= _mm_set1_ps(65536.0f);

    __m128i v_lo      = _mm_and_si128(v,msk_lo);          /* extract the 16 lowest significant bits of v                                   */
    __m128i v_hi      = _mm_srli_epi32(v,16);             /* 16 most significant bits of v                                                 */
    __m128  v_lo_flt  = _mm_cvtepi32_ps(v_lo);            /* No rounding                                                                   */
    __m128  v_hi_flt  = _mm_cvtepi32_ps(v_hi);            /* No rounding                                                                   */
            v_hi_flt  = _mm_mul_ps(cnst65536f,v_hi_flt);  /* No rounding                                                                   */
    return              _mm_add_ps(v_hi_flt,v_lo_flt);    /* Rounding may occur here, mul and add may fuse to fma for haswell and newer    */
}                                                         /* _mm_add_ps is guaranteed to give results with an error of at most 0.5 ULP     */

请注意,只要_mm_cvt_ps可以将两个片段都转换为浮点数而无需取整,则可以使用其他高位/低位分区。例如,具有20个高位和12个低位的分区将同样有效。



2> Paul R..:

这个功能存在于AVX-512中,但是如果你不能等到那时我唯一能建议的就是将unsigned int输入值转换成较小值的对,转换它们,然后再将它们加在一起,例如

inline __m128 _mm_cvtepu32_ps(const __m128i v)
{
    __m128i v2 = _mm_srli_epi32(v, 1);     // v2 = v / 2
    __m128i v1 = _mm_sub_epi32(v, v2);     // v1 = v - (v / 2)
    __m128 v2f = _mm_cvtepi32_ps(v2);
    __m128 v1f = _mm_cvtepi32_ps(v1);
    return _mm_add_ps(v2f, v1f); 
}

UPDATE

正如@wim在他的回答中指出的那样,上述解决方案的输入值失败了UINT_MAX.这是一个更强大但效率稍低的解决方案,它应该适用于整个uint32_t输入范围:

inline __m128 _mm_cvtepu32_ps(const __m128i v)
{
    __m128i v2 = _mm_srli_epi32(v, 1);                 // v2 = v / 2
    __m128i v1 = _mm_and_si128(v, _mm_set1_epi32(1));  // v1 = v & 1
    __m128 v2f = _mm_cvtepi32_ps(v2);
    __m128 v1f = _mm_cvtepi32_ps(v1);
    return _mm_add_ps(_mm_add_ps(v2f, v2f), v1f);      // return 2 * v2 + v1
}

推荐阅读
手机用户2402851155
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有