(数学者としての)C派生言語に対する私の嫌いの1つは、
(-1) % 8 // comes out as -1, and not 7
fmodf(-1,8) // fails similarly
最適なソリューションは何ですか?
C++では、テンプレートと演算子のオーバーロードの可能性がありますが、これらはどちらも私にとって不明瞭なものです。感謝して受け取った例。
まず第一に、(-1) % 8 == -1
という事実に頼ることさえできないことに注意したいと思います。信頼できる唯一のものは(x / y) * y + ( x % y) == x
です。ただし、剰余が負であるかどうかはimplementation-definedです。
ここでテンプレートを使用する理由intとlongsのオーバーロードはそうなります。
int mod (int a, int b)
{
int ret = a % b;
if(ret < 0)
ret+=b;
return ret;
}
これでmod(-1,8)のように呼び出すことができ、7のように表示されます。
編集:コードにバグが見つかりました。 bが負の場合、機能しません。だから私はこれが良いと思う:
int mod (int a, int b)
{
if(b < 0) //you can check for b == 0 separately and do what you want
return mod(a, -b);
int ret = a % b;
if(ret < 0)
ret+=b;
return ret;
}
参照:C++ 03パラグラフ5.6条項4:
二項/演算子は商を生成し、二項%演算子は最初の式を2番目の式で除算した剰余を生成します。 /または%の第2オペランドがゼロの場合、動作は未定義です。それ以外の場合、(a/b)* b + a%bはaと等しくなります。両方のオペランドが負でない場合、剰余は負ではありません。 ない場合、剰余の符号はimplementation-definedです。
以下は、両方のオペランドの正のOR負の整数OR小数値を処理するC関数です。
#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)
これは確かに数学的な観点から最もエレガントなソリューションです。ただし、整数の処理に堅牢であるかどうかはわかりません。 int-> fp-> intを変換すると、浮動小数点エラーが発生することがあります。
私はこのコードを非整数に使用し、別の関数を整数に使用しています。
注:N = 0をトラップする必要があります!
テスターコード:
#include <math.h>
#include <stdio.h>
float mod(float a, float N)
{
float ret = a - N * floor (a / N);
printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);
return ret;
}
int main (char* argc, char** argv)
{
printf ("fmodf(-10.2, 2.0) = %f.1 == FAIL! \n\n", fmodf(-10.2, 2.0));
float x;
x = mod(10.2f, 2.0f);
x = mod(10.2f, -2.0f);
x = mod(-10.2f, 2.0f);
x = mod(-10.2f, -2.0f);
return 0;
}
(注:CodePadから直接コンパイルして実行できます: http://codepad.org/UOgEqAMA )
出力:
fmodf(-10.2、2.0)= -0.20 ==失敗!
10.2 mod 2.0 = 0.2
10.2 mod -2.0 = -1.8
-10.2 mod 2.0 = 1.8
-10.2 mod -2.0 = -0.2
Bjarne Stroustrupが%
を剰余演算子、notモジュロ演算子としてラベル付けしていることに気づきました。
これはANSI CおよびC++仕様での正式な名前であり、用語の乱用が忍び込んでいるに違いありません。事実、これを知っている人はいますか?
しかし、これが当てはまる場合、Cのfmodf()関数(およびおそらく他の関数)は非常に誤解を招く可能性があります。それらにはfremf()などのラベルを付ける必要があります
整数の場合、これは簡単です。ただやる
(((x < 0) ? ((x % N) + N) : x) % N)
ここで、N
は正であり、x
のタイプで表現可能であると想定しています。お気に入りのコンパイラーはこれを最適化して、アセンブラーでたった1つのmod操作で終了するようにする必要があります。
正のモジュロを見つけるための最も簡単な一般関数はこれです。xの正の値と負の値の両方で機能します。
int modulo(int x,int N){
return (x % N + N) %N;
}
数学者にとって最善のソリューション¹は、Pythonを使用することです。
C++演算子のオーバーロードは、それとはほとんど関係がありません。組み込み型の演算子をオーバーロードすることはできません。必要なのは単なる関数です。もちろん、C++テンプレートを使用して、1つのコードだけで、関連するすべての型にその関数を実装できます。
標準Cライブラリは、名前を正しく思い出せば、浮動小数点型に対してfmod
を提供します。
整数の場合、常に非負の剰余(ユークリッド除算に対応)を返すC++関数テンプレートを定義できます...
#include <stdlib.h> // abs
template< class Integer >
auto mod( Integer a, Integer b )
-> Integer
{
Integer const r = a%b;
return (r < 0? r + abs( b ) : r);
}
...そしてa%b
の代わりにmod(a, b)
と書くだけです。
ここで、型Integer
は符号付き整数型である必要があります。
剰余の符号が除数の符号と同じである一般的な数学動作が必要な場合は、たとえば.
template< class Integer >
auto floor_div( Integer const a, Integer const b )
-> Integer
{
bool const a_is_negative = (a < 0);
bool const b_is_negative = (b < 0);
bool const change_sign = (a_is_negative != b_is_negative);
Integer const abs_b = abs( b );
Integer const abs_a_plus = abs( a ) + (change_sign? abs_b - 1 : 0);
Integer const quot = abs_a_plus / abs_b;
return (change_sign? -quot : quot);
}
template< class Integer >
auto floor_mod( Integer const a, Integer const b )
-> Integer
{ return a - b*floor_div( a, b ); }
…Integer
に同じ制約があり、それが符号付き型であること。
¹Pythonの整数除算は負の無限大に向かって丸められるため。
ああ、これも%デザインが嫌いです。
次のような方法で、配当を符号なしに変換できます。
unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider
result = (offset + dividend) % divider
ここで、オフセットはモジュールの(-INT_MIN)倍数に最も近いため、加算と減算はモジュロを変更しません。符号なしの型を持ち、結果は整数になることに注意してください。残念ながら、整数のオーバーフローを引き起こすため、値INT_MIN ...(-offset-1)を正しく変換できません。ただし、このメソッドには、定数除算器を使用する場合、演算ごとに追加の算術演算が1つしかなく(条件なし)の利点があるため、DSPのようなアプリケーションで使用できます。
仕切りが2である特別な場合がありますN (整数の2のべき乗)。モジュロは、単純な算術演算とビット単位の論理を使用して計算できます。
dividend&(divider-1)
例えば
x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15
より一般的でトリッキーでない方法は、この関数を使用してモジュロを取得することです(正の除算器でのみ動作します):
int mod(int x, int y) {
int r = x%y;
return r<0?r+y:r;
}
負の場合、これは単に正しい結果です。
また、あなたはトリックするかもしれません:
(p%q + q)%q
非常に短いですが、一般的に遅い2つの%-sを使用します。
この問題の別の解決策は、intではなくlong型の変数に使用することだと思います。
私は、%演算子が負の値を返していていくつかの問題を引き起こしているコードで作業していました([0,1]で均一なランダム変数を生成するために、本当に負の数値は必要ありません:))タイプロング、すべてがスムーズに実行され、結果はpythonで同じコードを実行したときに得られた結果と一致しました(複数のプラットフォームで同じ「乱数」を生成できるようにしたいので重要です) 。
これに基づいた古い質問に対する新しい回答があります Microsoft Researchの論文 とその中の参考文献。
C11およびC++ 11以降では、div
のセマンティクスがゼロへの切り捨てになっていることに注意してください([expr.mul]/4
を参照)。さらに、D
をd
で除算すると、C++ 11は商qT
および剰余rT
について以下を保証します
auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D));
ここで、signum
は、引数が<、==、>> 0であるかどうかに応じて、-1、0、+ 1にマッピングされます(ソースコードについては this Q&A を参照)。
切り捨てられた除算の場合、剰余の符号は配当の符号と等しくなりますD
、つまり-1 % 8 == -1
。 C++ 11は、std::div
関数も提供します。この関数は、切り捨てられた分割に従って、メンバーquot
およびrem
を持つ構造体を返します。
他にも可能な定義があります。いわゆるfloored divisionは、組み込みの切り捨てられた分割に関して定義できます。
auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));
フロア除算では、剰余の符号は除数の符号d
と等しくなります。 HaskellやOberonなどの言語には、フロア分割用の組み込み演算子があります。 C++では、上記の定義を使用して関数を作成する必要があります。
さらに別の方法はEuclidean divisionで、これは組み込みの切り捨てられた除算に関して定義することもできます
auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) != -1);
ユークリッド除算の場合、剰余の符号は常に正です。
/*警告:macro modは引数の副作用を複数回評価します。 */ #define mod(r、m)(((r)%(m))+((r)<0)?(m):0)
...または単に等価クラスの代表者を取得することに慣れます。
C++のサンプルテンプレート
template< class T >
T mod( T a, T b )
{
T const r = a%b;
return ((r!=0)&&((r^b)<0) ? r + b : r);
}
このテンプレートでは、剰余のC++の動作がゼロまたは被除数と同じ符号を持つ代わりに、返される剰余はゼロまたは除数(分母)と同じ符号(負の無限大への丸めと同等)になります(分子)(ゼロへの丸めに相当)。