在C语言中,如何将浮点数或双数除以2并乘幂i?

问题描述

我已经用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向我展示的是使用ldexpscalbn以及与{{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())。 vabsldexp的编码方式相同。与scalbn相乘的编码方式与我最初提出问题的方式相同。

附录2:,以显示根据chqrlie的建议编译的代码

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调用转换为单个指令。