【发布时间】:2022-03-10 22:02:15
【问题描述】:
两个无符号整数的算术平均值定义为:
mean = (a+b)/2
在 C/C++ 中直接实现它可能会溢出并产生错误的结果。正确的实现可以避免这种情况。一种编码方式可能是:
mean = a/2 + b/2 + (a%2 + b%2)/2
但这会产生相当多的典型编译器代码。在汇编程序中,这通常可以更有效地完成。例如,x86可以通过以下方式做到这一点(汇编伪代码,希望你明白):
ADD a,b ; addition, leaving the overflow condition in the carry bit
RCR a,1 ; rotate right through carry, effectively a division by 2
在这两条指令之后,结果在a,除法的余数在进位位。如果需要正确舍入,第三条ADC 指令必须将进位添加到结果中。
请注意,使用了 RCR 指令,该指令通过进位循环寄存器。在我们的例子中,它是一个位置的循环,因此前一个进位成为寄存器中的最高有效位,新的进位保存寄存器中的前一个 LSB。似乎 MSVC 甚至没有为此指令提供内在函数。
是否有一种已知的 C/C++ 模式可以被优化编译器识别,从而生成如此高效的代码?或者,更一般地说,是否有一种合理的方式如何在 C/C++ 源代码级别进行编程,以便编译器使用进位位来优化生成的代码?
编辑:
关于std::midpoint:https://www.youtube.com/watch?v=sBtAGxBh-XI的1小时讲座
哇!
【问题讨论】:
-
考虑
((wider_type)a+b)/2。 -
您应该澄清您正在寻找无符号算术平均值。您的“add + rcr”为有符号整数给出了错误的答案。许多编译器都有一个内在的“添加和报告执行”,您可以使用它。然后除以 2 并根据进位设置最高位,或使用旋转内在函数。
-
@sh- 是的,你提到“如果需要正确的舍入,第三条 ADC 指令必须将进位添加到结果中。” +1 的额外加法仅对有符号负数的舍入方向有意义。但是在有符号数字的情况下,进位没有设置,而是溢出。因此我很困惑。
-
@chux-ReinstateMonica:对于小于 reg 宽度的类型,加宽编译非常有效。令人惊讶的是,
uint64_t使用unsigned __int128作为更广泛的类型也相当不错:编译器实现高半 0/1,然后将shrd它放入。godbolt.org/z/sz53eEYh9 显示答案和 cmets 中提出的其他公式。 在 ARM64 上,总共只需要 3 条指令,adds/adcs/extr。因此,如果 ARM64 有 RCR,它只比你在 asm 中做的差 1。
标签: c++ c optimization compiler-optimization intrinsics