问题描述
我已经用C语言编写了处理32位宽整数值的Atmel微控制器SAM E70的代码。为了进行进一步的计算,我将整数值标准化为0 ... 1.0,如下所示:
#define DIV4294967296 ((double) 1.0) / ((double) 4294967296.0)
.
.
double doubleValue;
doubleValue = ((double) intValue) * DIV4294967296;
我知道我可以从doubleValue
的指数中减去32,从而避免更昂贵的乘法。我知道ldexp()
可以将指数乘以2乘以幂 i ,但是找不到任何可以显式读取,操纵和写回指数的东西一双。实际上执行所有这些步骤可能并不比执行乘法快,因此从指数中直接减去32将是理想的。通常在C中如何完成?更重要的是,如何用ARM的Cortex V7指令集做到最好?
附录:为回答Eric的问题,这是Atmel Studio 7向我展示的是使用ldexp
,scalbn
以及与{{1}乘法的反汇编代码}:
0x1p-32
似乎所有这些都不匹配任何ARM指令(例如C函数uint32_t intV = 123456;
ldr r3,[pc,#424]
str r3,[r7,#28]
double doubleV0 = ((double) intV) * DIV4096;
ldr r3,#36]
vmov s15,r3
vcvt.f64.u32 d7,s15
vldr d6,#272]
vmul.f64 d7,d7,d6
vstr d7,#24]
double doubleV1 = ldexp(intV,-32);
ldr r3,#28]
vmov s15,r3
vcvt.f64.u32 d7,s15
mvn r0,#31
vmov.f64 d0,d7
ldr r3,#408]
blx r3
vstr d0,#16]
double doubleV2 = scalbn(intV,#31
vmov.f64 d0,d7
ldr r3,#384]
blx r3
vstr d0,#8]
double doubleV3 = intV * 0x1p-32;
ldr r3,s15
vldr d6,#164]
vmul.f64 d7,d6
vstr d7,[r7]
直接编译为汇编指令fabs()
)。 vabs
和ldexp
的编码方式相同。与scalbn
相乘的编码方式与我最初提出问题的方式相同。
0x1p-32
在我看来,这是最便宜的实现。
最终判决:我喜欢chqrlie的回答,因为它可能对我们中间那些乘法太慢的人有用。但就我而言,我运行了一个基于中断的例程,并测量了我的初始代码和chqrlie的替代代码的执行时间,如果在GCC 9.3.1中使用了最佳优化(-O3),它们的运行时间完全相同。 >
解决方法
如果您可以断言double
是使用IEEE 754 double-precision binary floating-point format: binary64存储的,则其端序与64位整数具有相同的对齐要求,并且其值足够大,结果仍然是一个正常值,您可以使用此表达式直接修改表示形式,该表达式应编译为2或3条指令:
*(uint64_t *)&doubleValue -= 32ULL << 52;
然而,这种类型的Punning类型可能会引起攻击性优化器的麻烦,因为它违反了C别名规则,因为类型double
的值是通过指向不是字符指针的其他类型的指针访问的。可以通过union
使用一种更好的类型punning类型,该类型将对大多数编译器正确运行:
union { double d; uint64_t u; } u = doubleValue;
u.u -= 32ULL << 52;
doubleValue = u.d;
要完全避免C别名问题,可以使用memcpy
:
uint64_t u;
memcpy(&u,&doubleValue,sizeof u);
u -= 32ULL << 52;
memcpy(&doubleValue,&u,sizeof u);
一个好的优化编译器应该将这些memcpy
调用转换为单个指令。