私はそれを見つけることができないに違いないと感じています。C++ 関数がs とspow
以外の「パワー」関数を実装しない理由はありますか?float
double
実装が簡単であることはわかっていますが、標準ライブラリにあるはずの作業を行っているように感じます。堅牢な累乗関数 (つまり、一貫性のある明示的な方法でオーバーフローを処理する) は、書くのが楽しくありません。
私はそれを見つけることができないに違いないと感じています。C++ 関数がs とspow
以外の「パワー」関数を実装しない理由はありますか?float
double
実装が簡単であることはわかっていますが、標準ライブラリにあるはずの作業を行っているように感じます。堅牢な累乗関数 (つまり、一貫性のある明示的な方法でオーバーフローを処理する) は、書くのが楽しくありません。
の時点でC++11
、特別なケースが一連の電源機能(およびその他)に追加されました。C++11 [c.math] /11
すべてのfloat/double/long double
オーバーロードをリストした後(私の強調、言い換え):
さらに、パラメータに対応する引数が型または整数型である場合、パラメータに対応するすべての引数が効果的ににキャストされることを保証するのに十分な追加のオーバーロードが必要です。
double
double
double
double
したがって、基本的に、整数パラメーターは、操作を実行するためにdoubleにアップグレードされます。
以前C++11
(あなたの質問が尋ねられたとき)、整数のオーバーロードは存在しませんでした。
私は(私はかなり年をとっていますが)作成者とは密接に関係してC
おらず、標準を作成したANSI / ISO委員会の一部でもなかったので、これは必然的に私の意見です。私はそれが情報に基づいた意見だと思いたいのですが、私の妻があなたに言うように(頻繁にそして多くの励ましは必要ありません)、私は以前に間違っていました:-)C++
仮定は、それが価値があるものとして、次のようになります。
元のANSI以前にこのC
機能がなかったのは、まったく不要だったためだと思います。まず、整数の累乗を行うための完全に優れた方法がすでにありました(doubleを使用してから、単純に整数に変換し直し、変換する前に整数のオーバーフローとアンダーフローをチェックします)。
第二に、覚えておかなければならないもう1つのことは、本来の目的はC
システムプログラミング言語であり、その分野で浮動小数点が望ましいかどうかは疑問です。
最初のユースケースの1つはUNIXのコーディングであったため、浮動小数点はほとんど役に立たなかったでしょう。CのベースとなったBCPLも、パワーを使用しませんでした(メモリからの浮動小数点はまったくありませんでした)。
余談ですが、積分電力演算子は、おそらくライブラリ呼び出しではなく二項演算子でした。2つの整数を追加するのでは
x = add (y, z)
なく、ライブラリではなく適切な言語x = y + z
の一部を追加します。
第三に、統合力の実装は比較的些細なことなので、言語の開発者はより有用なものを提供するために時間を使うほうがよいことはほぼ確実です(機会費用に関する以下のコメントを参照)。
これは、元のにも関連していC++
ます。C
元の実装は事実上、コードを生成する単なるトランスレータであったため、の属性の多くを引き継いでいますC
。その当初の意図は、C-with-classes-plus-a-little-bit-of-extra-math-stuffではなく、C-with-classesでした。
以前に標準に追加されなかった理由についてはC++11
、標準設定機関が従うべき特定のガイドラインを持っていることを覚えておく必要があります。たとえば、ANSIは、新しい言語を作成するのではなくC
、既存の慣行を体系化することを特に任務としていました。そうでなければ、彼らは夢中になって私たちにエイダを与えたかもしれません:-)
その標準のその後の反復にも特定のガイドラインがあり、理論的根拠の文書に記載されています(言語自体の根拠ではなく、委員会が特定の決定を行った理由に関する根拠)。
たとえば、C99
理論的根拠文書は、C89
追加できるものを制限する2つの指針を具体的に示しています。
ガイドライン(必ずしもそれらの特定のものではない)は、個々のワーキンググループのために定められており、したがって、C++
委員会(および他のすべてのISOグループ)も制限します。
さらに、基準設定機関は、彼らが下すすべての決定に機会費用(あなたが下された決定のために何を放棄しなければならないかを意味する経済用語)があることを認識しています。たとえば、10,000ドルのユーバーゲーミングマシンを購入する機会費用は、約6か月間、残りの半分との心のこもった関係(またはおそらくすべての関係)です。
Eric Gunnersonは、Microsoft製品に常に物事が追加されるとは限らない理由について、-100ポイントの説明でこれをうまく説明しています。基本的に、機能は穴から100ポイントで始まるため、検討するためにはかなりの価値を追加する必要があります。
言い換えれば、統合されたパワーオペレーター(正直なところ、半ばまともなコーダーは10分で完成する可能性があります)またはマルチスレッドを標準に追加する必要がありますか?私自身は、後者を使用したいので、UNIXとWindowsでの異なる実装をいじくり回す必要はありません。
また、標準ライブラリ(ハッシュ、btree、赤黒木、辞書、任意のマップなど)の何千ものコレクションも見たいと思いますが、理論的根拠として次のように述べています。
標準は、実装者とプログラマーの間の条約です。
そして、標準化団体の実装者の数は、プログラマー(または少なくとも機会費用を理解していないプログラマー)の数をはるかに上回っています。これらすべてが追加された場合、次の標準C++
はC++215x
、300年後にコンパイラ開発者によって完全に実装されることになります。
とにかく、それはその問題についての私の(かなりボリュームのある)考えです。質ではなく量に基づいて投票が行われた場合、私はすぐに他のすべての人を水から吹き飛ばします。聞いてくれてありがとう :-)
とにかく、固定幅の整数型の場合、可能な入力ペアのほぼすべてが型をオーバーフローします。可能な入力の大部分に対して有用な結果をもたらさない関数を標準化することの用途は何ですか?
関数を便利にするには、大きな整数型が必要であり、ほとんどの大きな整数ライブラリが関数を提供します。
編集:質問に対するコメントで、static_rtti は「ほとんどの入力でオーバーフローが発生しますか?同じことが exp と double pow にも当てはまります。文句を言う人はいません。」と書いています。これは正しくありません。
exp
というのは論外なので (実際には私の主張をより強くすることになりますが) は脇に置き、 に焦点を当てましょうdouble pow(double x, double y)
。(x,y) ペアのどの部分に対して、この関数は何か役に立つか (つまり、単純なオーバーフローやアンダーフローではないか)?
実際には、意味のある入力ペアのごく一部のみに焦点を当てるつもりです。これpow
は、私の主張を証明するのに十分であるためです。x が正で |y| の場合 <= 1 の場合、pow
オーバーフローまたはアンダーフローしません。これは、すべての浮動小数点ペアのほぼ 4 分の 1 を構成します (非 NaN 浮動小数点数のちょうど半分が正であり、非 NaN 浮動小数点数の半分未満が 1 未満の大きさを持っています)。明らかに、有用な結果を生成する他の多くの入力ペアがありpow
ますが、それがすべての入力の少なくとも 4 分の 1 であることを確認しました。
ここで、固定幅 (つまり非 bignum) 整数べき乗関数を見てみましょう。入力が単純にオーバーフローしないのはどの部分ですか? 意味のある入力ペアの数を最大化するには、基数を符号付きにし、指数を符号なしにする必要があります。基数と指数が両方ともn
ビット幅であるとします。意味のある入力部分の境界を簡単に取得できます。
したがって、2^(2n) 個の入力ペアのうち、2^(n+1) + 2^(3n/2) 未満が意味のある結果を生成します。最も一般的な使用法である 32 ビット整数を見ると、これは、入力ペアの 1 パーセントの 1/1000 程度の何かが単純にオーバーフローしないことを意味します。
いずれにせよ、すべての整数乗を int で表す方法はないためです。
>>> print 2**-4
0.0625
簡潔な答え:
pow(x, n)
to が自然数であるという特殊化は、時間のパフォーマンスn
に役立つことがよくあります。しかし、標準ライブラリのジェネリックは、この目的にはまだかなり (驚くべきことに! ) うまく機能し、標準 C ライブラリに可能な限り含まれないようにすることが絶対に重要です。一方で、それは C++ 標準ライブラリや STL に含まれることをまったく妨げません。ある種の組み込みプラットフォームで使用することを誰も計画していないことは確かです。pow()
さて、長い答えです。
pow(x, n)
n
多くの場合、自然数に特化することではるかに高速化できます。私が書くほとんどすべてのプログラムで、この関数の独自の実装を使用する必要がありました (ただし、C で多くの数学プログラムを作成しています)。O(log(n))
特殊な操作は時間内に実行できますが、n
が小さい場合は、単純な線形バージョンの方が高速になる可能性があります。両方の実装を次に示します。
// Computes x^n, where n is a natural number.
double pown(double x, unsigned n)
{
double y = 1;
// n = 2*d + r. x^n = (x^2)^d * x^r.
unsigned d = n >> 1;
unsigned r = n & 1;
double x_2_d = d == 0? 1 : pown(x*x, d);
double x_r = r == 0? 1 : x;
return x_2_d*x_r;
}
// The linear implementation.
double pown_l(double x, unsigned n)
{
double y = 1;
for (unsigned i = 0; i < n; i++)
y *= x;
return y;
}
(私は左x
と戻り値を double として残しました。これは、の結果がpow(double x, unsigned n)
ほぼ同じ頻度で double に収まるためpow(double, double)
です。)
(はい、再帰的ですが、スタックの最大サイズはほぼ等しく、整数であるため、スタックを壊すpown
ことは絶対に不可能です。ただし、ハードウェア スタックが 3 ~ 8 個の関数呼び出しの深さしかない危険な PIC を除きます。)log_2(n)
n
n
パフォーマンスに関しては、園芸品種の能力に驚かれることでしょうpow(double, double)
。x
5 年前の IBM Thinkpad で、反復回数と10 に等しい反復を 1 億回テストしましたn
。このシナリオでは、pown_l
勝利しました。glibcpow()
には 12.0 ユーザー秒、pown
7.4 ユーザー秒、pown_l
わずか 6.5 ユーザー秒かかりました。ですから、それほど驚くべきことではありません。私たちは多かれ少なかれこれを期待していました。
次に、x
定数 (2.5 に設定) にしてn
、0 から 19 までを 1 億回ループさせました。今回は、まったく予想外に、glibcpow
が勝利し、圧勝しました! わずか 2.0 ユーザー秒かかりました。私pown
は9.6秒、pown_l
12.2秒かかりました。ここで何が起こったのですか?調べるために別のテストを行いました。
x
100万に等しいだけで上記と同じことをしました。今回pown
は9.6秒で勝利。pown_l
12.2 秒かかり、glibc pow は 16.3 秒かかりました。さぁ、クリアです!glibcpow
は、 が低い場合は 3 つよりもパフォーマンスが高くなりますが、高いx
場合は最悪x
です。x
が高いとき、低いときpown_l
に最高のパフォーマンスを発揮し、高いときに最高のパフォーマンスを発揮します。n
pown
x
ここに 3 つの異なるアルゴリズムがあり、それぞれが適切な状況下で他のアルゴリズムよりも優れたパフォーマンスを発揮します。したがって、最終的には、どれを使用するかは をどのように使用するかによって異なりますがpow
、適切なバージョンを使用する価値があり、すべてのバージョンを持っていると便利です。実際、次のような関数を使用して、アルゴリズムの選択を自動化することもできます。
double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
if (x_expected < x_threshold)
return pow(x, n);
if (n_expected < n_threshold)
return pown_l(x, n);
return pown(x, n);
}
x_expected
とがコンパイル時に決定される定数である限りn_expected
、場合によっては他のいくつかの警告とともに、そのソルトに値する最適化コンパイラは、pown_auto
関数呼び出し全体を自動的に削除し、3 つのアルゴリズムの適切な選択に置き換えます。(実際にこれを使おうとするなら、おそらく少しいじる必要があるでしょう。なぜなら、私は上で書いたものを正確にコンパイルしようとしたわけではないからです。;))
一方、glibcpow
は機能し、glibc はすでに十分に大きくなっています。C標準は、さまざまな組み込みデバイスを含めて移植可能であると想定されています(実際、どこの組み込み開発者も、glibcはすでに自分たちにとって大きすぎることに一般的に同意しています)。役に立つかもしれない代替アルゴリズム。それがC標準にない理由です。
脚注: 時間のパフォーマンス テストでは、関数に比較的寛大な最適化フラグ ( -s -O2
) を与えました。これは、システム (archlinux) で glibc をコンパイルするために使用された可能性が高いものより悪くはないにしても、同等である可能性が高いため、結果はおそらく次のようになります。公平。より厳密なテストを行うには、自分で glibc をコンパイルする必要がありますが、そうする気はまったくありません。以前はGentooを使っていたので、自動化してもかなり時間がかかったことを覚えています。結果は、私にとって十分に決定的 (またはむしろ決定的ではない) です。もちろん、これを自分で行うことも大歓迎です。
pow(x, n)
ボーナス ラウンド:すべての整数への特殊化は、正確な整数出力が必要な場合に役立ちます。これは発生します。p^N 要素の N 次元配列にメモリを割り当てることを検討してください。p^N を 1 つでもオフにすると、セグメンテーション違反がランダムに発生する可能性があります。
C++ に追加のオーバーロードがない理由の 1 つは、C との互換性のためです。
C++98 には のような関数がありますdouble pow(double, int)
が、これらは、C99 には含まれていないという理由で C++11 では削除されています。
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550
わずかに正確な結果を得ることは、わずかに異なる結果を得ることも意味します。
世界は常に進化しており、プログラミング言語も同様です。C 10 進数の TR ¹の4 番目の部分は、にいくつかの機能を追加し<math.h>
ます。これらの関数の 2 つのファミリは、この質問で興味深い場合があります。
pown
を取る関数。intmax_t
powr
浮動小数点数 (x
および) を取り、式でべき乗をy
計算する関数。x
y
exp(y*log(x))
標準派は最終的に、これらの機能を標準ライブラリに統合するのに十分有用であると判断したようです。ただし、これらの関数は、2 進および 10 進浮動小数点数のISO/IEC/IEEE 60559:2011規格で推奨されているためです。C89 の時点でどの「標準」が守られていたかはわかりませんが、今後の の進化は、おそらくISO/IEC/IEEE 60559標準<math.h>
の将来の進化に大きく影響されるでしょう。
10 進数の TR の 4 番目の部分は、C2x (次の主要な C リビジョン) には含まれず、後でオプション機能として含まれる可能性があることに注意してください。TR のこの部分を将来の C++ リビジョンに含めるつもりはありません。
¹ 進行中のドキュメントの一部をここで見つけることができます。
おそらく、プロセッサの ALU が整数に対してそのような関数を実装していないためですが、そのような FPU 命令があります (Stephen が指摘しているように、実際にはペアです)。したがって、実際には、整数演算を使用して実装するよりも、double にキャストし、double で pow を呼び出し、オーバーフローをテストしてキャスト バックする方が高速でした。
(1 つには、対数は乗算のべき乗を減らしますが、整数の対数はほとんどの入力に対して多くの精度を失います)
スティーブンは正しい、最新のプロセッサではこれはもはや真実ではありませんが、数学関数が選択されたときの C 標準 (C++ は C 関数を使用しただけです) は、今では 20 年前のものですか?
非常に単純な理由:
5^-2 = 1/25
STL ライブラリのすべては、想像できる最も正確で堅牢なものに基づいています。確かに、int は (1/25 から) ゼロに戻りますが、これは不正確な答えになります。
同意します、場合によっては奇妙です。