我想在GHC Haskell编译器中实现等效的C uint
- to - double
强制转换。我们已使用int
或double
实施FILD
- 至 - CVTSI2SD
。是否存在这些操作的无符号版本,或者我应该在转换之前将uint
的最高位归零(从而丢失范围)?
答案 0 :(得分:4)
您可以利用IEEE双精度格式的某些属性,并将无符号值解释为尾数的一部分,同时添加一些精心设计的指数。
Bits 63 62-52 51-0
S Exp Mantissa
0 1075 20 bits 0, followed by your unsigned int
1075来自双指数的IEEE指数偏差(1023)和尾数的52位“移位”量。请注意,有一个隐含的“1”引导尾数,需要在以后减去。
所以:
double uint32_to_double(uint32_t x) {
uint64_t xx = x;
xx += 1075ULL << 52; // add the exponent
double d = *(double*)&xx; // or use a union to convert
return d - (1ULL << 52); // 2 ^^ 52
}
如果您的平台上没有本机64位,则使用SSE进行整数步骤的版本可能会有所帮助,但这当然取决于此。
在我的平台上,这将编译为
0000000000000000 <uint32_to_double>:
0: 48 b8 00 00 00 00 00 movabs $0x4330000000000000,%rax
7: 00 30 43
a: 89 ff mov %edi,%edi
c: 48 01 f8 add %rdi,%rax
f: c4 e1 f9 6e c0 vmovq %rax,%xmm0
14: c5 fb 5c 05 00 00 00 vsubsd 0x0(%rip),%xmm0,%xmm0
1b: 00
1c: c3 retq
看起来很不错。 0x0(%rip)
是神奇的双常数,如果内联一些指令,如高32位归零和常量重载将消失。
答案 1 :(得分:3)
有一种更好的方法
__m128d _mm_cvtsu32_sd(__m128i n) {
const __m128i magic_mask = _mm_set_epi32(0, 0, 0x43300000, 0);
const __m128d magic_bias = _mm_set_sd(4503599627370496.0);
return _mm_sub_sd(_mm_castsi128_pd(_mm_or_si128(n, magic_mask)), magic_bias);
}
答案 2 :(得分:3)
有人说,&#34;好艺术家复制;伟大的艺术家窃取&#34;。所以我们可以检查其他编译器编写者如何解决这个问题。我使用了一个简单的片段:
volatile unsigned int x;
int main()
{
volatile double y = x;
return y;
}
(添加挥发物以确保编译器不会优化转换)
结果(跳过不相关的指示):
__real@41f0000000000000 DQ 041f0000000000000r ; 4.29497e+009
mov eax, DWORD PTR ?x@@3IC ; x
fild DWORD PTR ?x@@3IC ; x
test eax, eax
jns SHORT $LN4@main
fadd QWORD PTR __real@41f0000000000000
$LN4@main:
fstp QWORD PTR _y$[esp+8]
所以基本上编译器在设置符号位时添加调整值。
mov eax, DWORD PTR ?x@@3IC ; x
pxor xmm0, xmm0
cvtsi2sd xmm0, rax
movsdx QWORD PTR y$[rsp], xmm0
无需在此调整,因为编译器知道rax
将清除符号位。
__xmm@41f00000000000000000000000000000 DB 00H, 00H, 00H, 00H, 00H, 00H, 00H
DB 00H, 00H, 00H, 00H, 00H, 00H, 00H, 0f0H, 'A'
mov eax, DWORD PTR ?x@@3IC ; x
movd xmm0, eax
cvtdq2pd xmm0, xmm0
shr eax, 31 ; 0000001fH
addsd xmm0, QWORD PTR __xmm@41f00000000000000000000000000000[eax*8]
movsd QWORD PTR _y$[esp+8], xmm0
这使用无分支代码添加0或魔术调整,具体取决于符号位是清除还是设置。
答案 3 :(得分:2)
我们已经使用FILD实现了int-to-double ...
是否有这些操作的无符号版本
如果你想要使用x87 FILD操作码,只需将uint64转换为uint63(div 2),然后将其转换为2,但已经为double,因此x87 uint64-to-double转换需要在头顶上执行一次FMUL
示例: 0xFFFFFFFFFFFFFFFFU - &gt; + 1.8446744073709551e + 0019
它无法在严格的表单规则中发布代码示例。我稍后再试。
//inline
double u64_to_d(unsigned _int64 v){
//volatile double res;
volatile unsigned int tmp=2;
_asm{
fild dword ptr tmp
//v>>=1;
shr dword ptr v+4, 1
rcr dword ptr v, 1
fild qword ptr v
//save lsb
//mov byte ptr tmp, 0
//rcl byte ptr tmp, 1
//res=tmp+res*2;
fmulp st(1),st
//fild dword ptr tmp
//faddp st(1),st
//fstp qword ptr res
}
//return res;
//fld qword ptr res
}
VC产生x86输出
//inline
double u64_to_d(unsigned _int64 v){
55 push ebp
8B EC mov ebp,esp
81 EC 04 00 00 00 sub esp,04h
//volatile double res;
volatile unsigned int tmp=2;
C7 45 FC 02 00 00 00 mov dword ptr [tmp], 2
_asm{
fild dword ptr tmp
DB 45 FC fild dword ptr [tmp]
//v>>=1;
shr dword ptr v+4, 1
D1 6D 0C shr dword ptr [ebp+0Ch],1
rcr dword ptr v, 1
D1 5D 08 rcr dword ptr [v],1
fild qword ptr v
DF 6D 08 fild qword ptr [v]
//save lsb
// mov byte ptr [tmp], 0
//C6 45 FC 00 mov byte ptr [tmp], 0
// rcl byte ptr tmp, 1
//D0 55 FC rcl byte ptr [tmp],1
//res=tmp+res*2;
fmulp st(1),st
DE C9 fmulp st(1),st
// fild dword ptr tmp
//DB 45 FC fild dword ptr [tmp]
// faddp st(1),st
//DE C1 faddp st(1),st
//fstp qword ptr res
//fstp qword ptr [res]
}
//return res;
//fld qword ptr [res]
8B E5 mov esp,ebp
5D pop ebp
C3 ret
}
我发布了(可能是我手动删除了文本文件中所有未修正的ascii字符)。
答案 4 :(得分:1)
如果我正确理解你,你应该能够将32位uint移动到堆栈上的临时区域,将下一个dword归零,然后使用fild qword ptr将现在的64位无符号整数加载为双