> _mm_cvtps_epi32()
> _mm_cvtepi32_ps()
但是没有双精度和64位整数的等价物.换句话说,他们缺少:
> _mm_cvtpd_epi64()
> _mm_cvtepi64_pd()
似乎AVX也没有.
什么是模拟这些内在函数的最有效的方式?
解决方法
>如果你不在乎无限或NaN.
>对于双重 – int64_t,你只关心范围[-2 ^ 51,2 ^ 51]中的值.
>对于双重 – uint64_t,你只关心[0,2 ^ 52]范围内的值.
双 – > uint64_t中
// Only works for inputs in the range: [0,2^52) __m128i double_to_uint64(__m128d x){ x = _mm_add_pd(x,_mm_set1_pd(0x0010000000000000)); return _mm_xor_si128( _mm_castpd_si128(x),_mm_castpd_si128(_mm_set1_pd(0x0010000000000000)) ); }
双 – >的int64_t
// Only works for inputs in the range: [-2^51,2^51] __m128i double_to_int64(__m128d x){ x = _mm_add_pd(x,_mm_set1_pd(0x0018000000000000)); return _mm_sub_epi64( _mm_castpd_si128(x),_mm_castpd_si128(_mm_set1_pd(0x0018000000000000)) ); }
uint64_t – >双
// Only works for inputs in the range: [0,2^52) __m128d uint64_to_double(__m128i x){ x = _mm_or_si128(x,_mm_castpd_si128(_mm_set1_pd(0x0010000000000000))); return _mm_sub_pd(_mm_castsi128_pd(x),_mm_set1_pd(0x0010000000000000)); }
int64_t – >双
// Only works for inputs in the range: [-2^51,2^51] __m128d int64_to_double(__m128i x){ x = _mm_add_epi64(x,_mm_castpd_si128(_mm_set1_pd(0x0018000000000000))); return _mm_sub_pd(_mm_castsi128_pd(x),_mm_set1_pd(0x0018000000000000)); }
舍入行为:
>对于双 – > uint64_t转换,舍入在当前四舍五入模式后正常工作. (通常是圆均匀的)
>对于双 – > int64_t转换,除了截断之外,舍入将遵循所有模式的当前舍入模式.如果当前四舍五入模式被截断(向零舍入),它将实际上向负无穷大.
它是如何工作的?
尽管这个技巧只有2条指令,但并不完全不言自明.
关键是要认识到对于双精度浮点,范围[2 ^ 52,2 ^ 53]中的值具有正好位于尾数最低位的“二进制位置”.换句话说,如果你清除指数和符号位,尾数就变成了整数表示.
要将x从double转换为> uint64_t,你添加的魔术数字M是浮点值2 ^ 52.这将x置于[2 ^ 52,2 ^ 53]的“归一化”范围内,并且方便地舍弃小数部分位.
现在剩下的就是删除高12位.这很容易通过掩蔽它来完成.最快的方法是认识到那些高12位与M的相同.所以我们可以简单地减去XOR或者XOR,而不是引入额外的掩码常数.XOR具有更多的吞吐量.
从uint64_t转换为>这个过程恰恰相反.您返回M的指数位.然后通过在浮点中减去M来对数字进行非归一化.
由于您需要处理2的补码符号扩展,因此签名的整数转换稍微复杂一些.我会把这些作为读者的练习.
相关:A fast method to round a double to a 32-bit int explained