整数演算のみを使用して、C++で2つの符号なし整数を「安全に」平均化したいと思います。
私が「安全に」とは、オーバーフロー(および考えられるその他のこと)を回避することを意味します。
たとえば、平均化 200 そして 5000 は簡単だ:
unsigned int a = 200;
unsigned int b = 5000;
unsigned int average = (a + b) / 2; // Equals: 2600 as intended
しかしの場合 4294967295 そして 5000 その後:
unsigned int a = 4294967295;
unsigned int b = 5000;
unsigned int average = (a + b) / 2; // Equals: 2499 instead of 2147486147
私が思いついた最高のものは:
unsigned int a = 4294967295;
unsigned int b = 5000;
unsigned int average = (a / 2) + (b / 2); // Equals: 2147486147 as expected
より良い方法はありますか?
あなたの最後のアプローチは有望なようです。 aとbの最下位ビットを手動で検討することで、これを改善できます。
unsigned int average = (a / 2) + (b / 2) + (a & b & 1);
これにより、aとbの両方が奇数の場合に正しい結果が得られます。
unsigned int average = low + ((high - low) / 2);
[〜#〜]編集[〜#〜]
関連記事は次のとおりです。 http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
両方の数値が奇数(たとえば5と7)の場合、メソッドは正しくありません。平均は6ですが、メソッド#3は5を返します。
これを試して:
average = (a>>1) + (b>>1) + (a & b & 1)
数学演算子のみ:
average = a/2 + b/2 + (a%2) * (b%2)
少しx86インラインアセンブリ(GNU C構文)を気にしない場合は、追加後に rotate-with-carry を使用してフルの上位32ビットを配置するというsupercatの提案を利用できます。 33ビットの結果をレジスタに入れます。
もちろん、通常はインラインアセンブラを使用する必要がありますいくつかの最適化を無効にするためです( https://gcc.gnu.org/ wiki/DontUseInlineAsm )。しかし、ここでとにかく行きます:
// works for 64-bit long as well on x86-64, and doesn't depend on calling convention
unsigned average(unsigned x, unsigned y)
{
unsigned result;
asm("add %[x], %[res]\n\t"
"rcr %[res]"
: [res] "=r" (result) // output
: [y] "%0"(y), // input: in the same reg as results output. Commutative with next operand
[x] "rme"(x) // input: reg, mem, or immediate
: // no clobbers. ("cc" is implicit on x86)
);
return result;
}
%
修飾子 コンパイラに引数が可換であることを伝えることは、yを定数またはポインタデレフ(メモリ)として関数を呼び出して、私が試した場合、実際にはより良いasmを作成するのに役立ちませんオペランド)。おそらく、出力オペランドに一致制約を使用すると、読み取り/書き込みオペランドでは使用できないため、それが無効になります。
ご覧のとおり、 Godboltコンパイラエクスプローラーで 、これは正しくコンパイルされ、同じインラインasmでオペランドをunsigned long
に変更するバージョンも同様です。ただし、clang3.9はそれを台無しにし、"m"
制約に"rme"
オプションを使用することを決定したため、メモリに格納し、メモリオペランドを使用します。
RCR-by-oneはそれほど遅くはありませんが、Skylakeでは3 uopsであり、2サイクルのレイテンシーがあります。 RCRにシングルサイクルのレイテンシーがあるAMDCPUに最適です。 (出典: Agner Fogの命令テーブル 、x86パフォーマンスリンクについては x86 タグwikiも参照してください)。 @sellibitzeのバージョンよりはまだ良いですが、@ Sheldonの順序に依存するバージョンよりは悪いです。 (Godboltのコードを参照)
ただし、インラインasmは定数伝播などの最適化を無効にするため、その場合は純粋なC++バージョンの方が適していることに注意してください。
そして正解は...
(A&B)+((A^B)>>1)
あなたが持っているものは大丈夫ですが、3と3の平均は2であると主張するという小さな詳細があります。あなたはそれを望まないと思います。幸いなことに、簡単な修正があります。
unsigned int average = a/2 + b/2 + (a & b & 1);
これは、両方の部門が切り捨てられた場合に、平均を引き上げるだけです。
コードが組み込みマイクロ用であり、速度が重要な場合は、アセンブリ言語が役立つ場合があります。多くのマイクロコントローラでは、加算の結果は当然キャリーフラグになり、レジスタに戻すための命令が存在します。 ARMでは、平均的な操作(レジスタ内のソースと宛先)は2つの命令で実行できます。 C言語に相当するものは、少なくとも5つ、おそらくそれよりもかなり多くなる可能性があります。
ちなみに、Wordサイズが短いマシンでは、違いがさらに大きくなる可能性があります。 8ビットPIC-18シリーズでは、2つの32ビット数を平均すると12命令が必要になります。シフト、追加、および修正を行うには、シフトごとに5つの命令、追加に8つ、修正に8つの命令が必要になるため、26になります(2.5倍の違いではありませんが、絶対的にはもっと重要です)。
C++ 20では、 std::midpoint
を使用できます。
template <class T>
constexpr T midpoint(T a, T b) noexcept;
std::midpoint
を紹介した論文 P0811R は、このスニペットを推奨しました(C++ 11での作業に少し採用されました):
#include <type_traits>
template <typename Integer>
constexpr Integer midpoint(Integer a, Integer b) noexcept {
using U = std::make_unsigned<Integer>::type;
return a>b ? a-(U(a)-b)/2 : a+(U(b)-a)/2;
}
完全を期すために、このペーパーの変更されていないC++ 20の実装を次に示します。
constexpr Integer midpoint(Integer a, Integer b) noexcept {
using U = make_unsigned_t<Integer>;
return a>b ? a-(U(a)-b)/2 : a+(U(b)-a)/2;
}