web-dev-qa-db-ja.com

(-1)^ nを取得する正しい方法は何ですか?

多くのアルゴリズムは_(-1)^n_(両方整数)を計算する必要があり、通常は系列の要素として計算されます。つまり、奇数nの場合は_-1_、偶数nの場合は_1_の係数です。 C++環境では、次のことがよく見られます。

_#include<iostream>
#include<cmath>
int main(){
   int n = 13;
   std::cout << std::pow(-1, n) << std::endl;
}
_

より良い、または通常の慣習は何ですか?(または何か他のもの)、

_std::pow(-1, n)
std::pow(-1, n%2)
(n%2?-1:1)
(1-2*(n%2))  // (gives incorrect value for negative n)
_

編集:

さらに、ユーザー@SeverinPappadeuxは、(グローバル?)配列ルックアップに基づく別の代替案を提案しました。それの私のバージョンは:

_const int res[] {-1, 1, -1}; // three elements are needed for negative modulo results
const int* const m1pow = res + 1; 
...
m1pow[n%2]
_

これはおそらく問題を解決するつもりはありませんが、発行されたコードを使用することにより、いくつかのオプションを破棄できます。

最初に最適化を行わない場合、最終的な候補は次のとおりです。

_   1 - ((n & 1) << 1);
_

(7操作、メモリアクセスなし)

_  mov eax, DWORD PTR [rbp-20]
  add eax, eax
  and eax, 2
  mov edx, 1
  sub edx, eax
  mov eax, edx
  mov DWORD PTR [rbp-16], eax
_

そして

_   retvals[n&1];
_

(5つの操作、メモリ-レジスタ??アクセス)

_  mov eax, DWORD PTR [rbp-20]
  and eax, 1
  cdqe
  mov eax, DWORD PTR main::retvals[0+rax*4]
  mov DWORD PTR [rbp-8], eax
_

今最適化あり(-O3)

_   1 - ((n & 1) << 1);
_

(4操作、メモリアクセスなし)

_  add edx, edx
  mov ebp, 1
  and edx, 2
  sub ebp, edx
_

_  retvals[n&1];
_

(4つの操作、メモリ-登録?-アクセス)

_  mov eax, edx
  and eax, 1
  movsx rcx, eax
  mov r12d, DWORD PTR main::retvals[0+rcx*4]
_

_   n%2?-1:1;
_

(4つの操作、メモリアクセスなし)

_  cmp eax, 1
  sbb ebx, ebx
  and ebx, 2
  sub ebx, 1
_

テストは here です。操作をすべて一緒に回避しない意味のあるコードを作成するには、いくつかのアクロバットが必要でした。

結論(今のところ)

したがって、最終的にはレベルの最適化と表現力に依存します。

  • 1 - ((n & 1) << 1);always優れていますが、あまり表現力がありません。
  • _retvals[n&1];_は、メモリアクセスに対して料金を支払います。
  • _n%2?-1:1;_は表現力があり優れていますが、最適化が必要です。
52
alfC

高度な機能を使いたい場合、_(n & 1)_の代わりに_n % 2_を使用し、_<< 1_の代わりに_* 2_を使用できます。
そのため、8086プロセッサで計算する最速の方法は次のとおりです。

1 - ((n & 1) << 1)

この回答の出所を明確にしたいだけです。元のポスターalfCは、(-1)^ nを計算するさまざまな方法を投稿するという優れた仕事をしました。
昨今のプロセッサーは高速であり、コンパイラーの最適化も私たちと同じくらい優れています通常操作。
1パスコンパイラが地球を支配し、MUL操作が新しく退廃的な時代がありました。当時、2のべき乗演算は不必要な最適化への誘いでした。

49
Eli Algranti

通常、実際に(-1)^nを計算するのではなく、代わりに現在の符号を追跡し(数値は-1または1のいずれかとして)、演算ごとに反転します(sign = -sign)、 nを順番に処理するときにこれを行うと、同じ結果が得られます。

編集:私がこれをお勧めする理由の一部は、実際のセマンティック値が表現(-1)^nであることがめったにないためであることに注意してください。

37
Guvante

まず、私が知っている最速のisOddテスト(インラインメソッド)

/**
* Return true if the value is odd
* @value the value to check
*/
inline bool isOdd(int value)
{
return (value & 1);
}

次に、このテストを利用して、奇数の場合は-1を返し、それ以外の場合は1を返します((-1)^ Nの実際の出力です)。

/**
* Return the computation of (-1)^N
* @n the N factor
*/
inline int minusOneToN(int n)
{
return isOdd(n)?-1:1;
}

最後に、@ Guvanteで提案されているように、値の符号を反転するだけで乗算を省略できます(minusOneToN関数の使用を避けます)。

/**
* Example of the general usage. Avoids a useless multiplication
* @value The value to flip if it is odd
*/
inline int flipSignIfOdd(int value)
{
return isOdd(value)?-value:value;
}
7
Flavien Volken

多くのアルゴリズムは、(-1)^ n(両方とも整数)を計算する必要があり、通常は系列の係数として計算されます。つまり、奇数nの場合は-1、偶数nの場合は1の係数です。

代わりに、シリーズを-xの関数として評価することを検討してください。

3
Ian Ollmann

それがあなたが必要とする速度なら、ここに行きます...

int inline minus_1_pow(int n) {
    static const int retvals[] {1, -1}; 
    return retvals[n&1];
}

最適化が11になったVisual C++コンパイラは、これを2つの機械語命令にコンパイルしますが、どちらも分岐ではありません。 retvals配列も最適化されるため、キャッシュミスはありません。

2
Jive Dadson

一連の計算を実行する場合は、評価を完全にスキップして、正のループと負のループで計算を処理してみませんか?

(1 + x)の自然対数を近似するテイラー級数展開は、このタイプの問題の完璧な例です。各項には(-1)^(n + 1)または(1)^(n-1)があります。この係数を計算する必要はありません。 2つの条件ごとに1つのループを実行するか、奇数の条件と偶数の条件に1つずつ、2つのループを実行することで、問題を「スライス」できます。

もちろん、計算はその性質上、実数のドメインに対するものであるため、とにかく個々の項を評価するために浮動小数点プロセッサを使用します。それを行うことを決定したら、自然対数のライブラリ実装を使用する必要があります。しかし、何らかの理由でそうしないと決めた場合は、-1のn乗の値を計算するサイクルを無駄にしないほうが確かに速くなりますが、それほど多くはありません。

おそらく、それぞれを別々のスレッドで実行することもできます。多分問題はベクトル化することもできます。

1
Fred Mitchell

どうですか

(1 - (n%2)) - (n%2)

n%2ほとんどの場合、1回だけ計算されます

更新

実際には、最も簡単で最も正しい方法はテーブルを使用することです

const int res[] {-1, 1, -1};

return res[n%2 + 1];
1