我已经用 C 语言为 Atmel 的微 Controller SAM E70 编写了处理 32 位宽整数值的代码。为了进一步计算,我将整数值标准化为 0...1.0,如下所示:
#define DIV4294967296 ((double) 1.0) / ((double) 4294967296.0)
.
.
double doubleValue;
doubleValue = ((double) intValue) * DIV4294967296;
我知道我可以从 doubleValue
的指数中减去 32,从而避免更昂贵的乘法。我知道 ldexp()
允许将指数乘以 2 的 i 次幂,但我找不到任何可以让我显式读取、操作、并写回 double 的指数。执行所有这些步骤实际上可能并不比执行乘法更快,因此从指数中直接减去 32 是理想的。这在 C 语言中通常是如何完成的?更重要的是,如何使用 ARM 的 Cortex V7 指令集最好地完成此任务?
附录:为了回答 Eric 的问题,这是 Atmel Studio 7 向我展示的反汇编代码,用于使用 ldexp
、scalbn
和与 0x1p-32
相乘:
uint32_t intV = 123456;
ldr r3, [pc, #424]
str r3, [r7, #28]
double doubleV0 = ((double) intV) * DIV4096;
ldr r3, [r7, #36]
vmov s15, r3
vcvt.f64.u32 d7, s15
vldr d6, [pc, #272]
vmul.f64 d7, d7, d6
vstr d7, [r7, #24]
double doubleV1 = ldexp(intV, -32);
ldr r3, [r7, #28]
vmov s15, r3
vcvt.f64.u32 d7, s15
mvn r0, #31
vmov.f64 d0, d7
ldr r3, [pc, #408]
blx r3
vstr d0, [r7, #16]
double doubleV2 = scalbn(intV, -32);
ldr r3, [r7, #28]
vmov s15, r3
vcvt.f64.u32 d7, s15
mvn r0, #31
vmov.f64 d0, d7
ldr r3, [pc, #384]
blx r3
vstr d0, [r7, #8]
double doubleV3 = intV * 0x1p-32;
ldr r3, [r7, #28]
vmov s15, r3
vcvt.f64.u32 d7, s15
vldr d6, [pc, #164]
vmul.f64 d7, d7, d6
vstr d7, [r7]
看起来这些都不匹配任何 ARM 指令(例如 C 函数 fabs() 直接编译为汇编指令 vabs )。 ldexp
和 scalbn
的编码方式相同。与 0x1p-32
的乘法的编码方式与我最初的乘法相同,这让我提出了我的问题。
附录 2:显示它根据 chqrlie 的建议编译成的代码:
double doubleV4 = ((double) intV);
vstr d7, [r7]
*(uint64_t *)&doubleV4 -= 32ULL << 52;
mov r3, r7
ldrd r2, r3, [r3]
mov r1, r7
adds r4, r2, #0
adc r5, r3, #4261412864
strd r4, r5, [r1]
在我看来,这是最便宜的实现。
最终结论:我喜欢 chqrlie 的答案,因为它可能对我们当中乘法太慢的人有用。但就我而言,我运行了一个基于中断的例程,并测量了我的初始代码和 chqrlie 的替代方案的执行时间,如果最佳优化 (-O3) 与 GCC 9.3.1 一起使用,它们的运行时间完全相同。
最佳答案
如果您可以断言 double
是使用 IEEE 754 double-precision binary floating-point format: binary64 存储的,具有与 64 位整数相同的字节序和对齐要求,并且其值足够大,结果仍然是正常值,您可以直接使用此表达式来破解表示形式,该表达式应编译为 2 或 3 条指令:
*(uint64_t *)&doubleValue -= 32ULL << 52;
然而,这种形式的类型双关可能会给激进的优化器带来麻烦,因为它违反了 C 别名规则,因为类型 double
的值是通过指向非字符指针的不同类型的指针访问的。可以通过union
使用更好的类型双关形式,它可以在大多数编译器中正常工作:
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
调用转换为单个指令。
关于c - 在C中,如何将 float 或 double 除以2的i次方?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63440960/