88

(数学者としての) C 派生言語に対する私のペットの嫌いな点の 1 つは、

(-1) % 8 // comes out as -1, and not 7

fmodf(-1,8) // fails similarly

最善の解決策は何ですか?

C++ では、テンプレートと演算子のオーバーロードが可能ですが、どちらも私にとっては難解です。感謝して受け取った例。

4

16 に答える 16

77

まず第一に、という事実に頼ることさえできないことに注意したいと思います(-1) % 8 == -1。あなたが頼ることができる唯一のものはそれ(x / y) * y + ( x % y) == xです。ただし、剰余が負かどうかは処理系定義です。

参照: C++03 パラグラフ 5.6 節 4:

二項 / 演算子は商を生成し、二項 % 演算子は最初の式を 2 番目の式で割った剰余を生成します。/ または % の 2 番目のオペランドがゼロの場合、動作は未定義です。それ以外の場合、(a/b)*b + a%b は a に等しくなります。両方のオペランドが負でない場合、残りは負ではありません。そうでない場合、剰余の符号は実装定義です。

ここでは、除数からの剰余の減算の結果を被除数から減算できるように、両方の負のオペランドを処理するバージョンに従います。これにより、実際の除算の下限なります。は 7 になり、-3 になります。mod(-1,8)mod(13, -8)

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;
}
于 2010-10-23T09:25:06.670 に答える
14

これは、両方のオペランドの正または負の整数または小数値を処理する C 関数です。

#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)

これは確かに、数学的な観点から最も洗練されたソリューションです。ただし、整数の処理に堅牢かどうかはわかりません。int -> fp -> int の変換時に浮動小数点エラーが発生することがあります。

非 int にはこのコードを使用し、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

于 2010-10-23T09:15:30.473 に答える
9

Bjarne Stroustrupがモジュロ演算子ではなく、剰余演算子%としてラベルを付けていることに気付きました。

これは ANSI C & C++ 仕様での正式な名前であり、用語の乱用が忍び込んでいるに違いありません。これを事実として知っている人はいますか?

しかし、これが事実である場合、C の fmodf() 関数 (およびおそらく他の関数) は非常に誤解を招くものです。fremf() などのラベルを付ける必要があります。

于 2010-11-01T03:54:39.633 に答える
6

整数の場合、これは簡単です。やるだけ

(((x < 0) ? ((x % N) + N) : x) % N)

Nここで、それは正であり、の型で表現可能であると想定していxます。あなたのお気に入りのコンパイラは、これを最適化して、アセンブラでたった 1 つの mod 操作で終わるようにすることができるはずです。

于 2010-10-23T09:25:53.020 に答える
3

数学者にとっての最善の解決策は、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);
}

mod(a, b)...の代わりに書くだけです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 の整数除算は負の無限大に向かって丸められるためです。

于 2010-10-23T09:24:36.813 に答える
2

この問題に対する別の解決策は、int 型ではなく long 型の変数を使用することだと思います。

% 演算子が負の値を返し、いくつかの問題を引き起こしたコードに取り組んでいました ([0,1] で一様確率変数を生成するために、実際には負の数は必要ありません:))、変数を次のように切り替えた後long と入力すると、すべてがスムーズに実行され、結果は Python で同じコードを実行したときに得られたものと一致しました (複数のプラットフォームで同じ「乱数」を生成できるようにしたかったので、私にとって重要です。

于 2012-10-28T14:37:46.727 に答える
2

ああ、これも% design嫌い…。

次のような方法で、被除数を符号なしに変換できます。

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

これは非常に短いですが、一般的に遅い %-s を 2 つ使用します。

于 2010-10-23T18:56:41.163 に答える
2

ブランチを使用せず、mod を 1 つだけ使用するソリューションの場合、次のことができます。

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}
于 2020-04-02T18:42:37.263 に答える
1
/* 警告: マクロ mod は引数の副作用を複数回評価します。*/
#define mod(r,m) (((r) % (m)) + ((r)<0)?(m):0)

...または、等価クラスの代表者を取得することに慣れてください。

于 2010-10-23T09:30:33.497 に答える
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++ の動作ではありません (分子) (ゼロ方向への丸めと同等)。

于 2016-07-17T23:58:10.063 に答える
-1
unsigned mod(int a, unsigned b) {
    return (a >= 0 ? a % b : b - (-a) % b);
}
于 2014-03-07T01:19:38.610 に答える
-1
define  MOD(a, b)       ((((a)%(b))+(b))%(b))
于 2012-11-28T15:06:36.650 に答える
-2

私はするだろう:

((-1)+8) % 8 

これは、必要に応じて7を与えるモジュロを実行する前に、後者の数値を最初の数値に追加します。これは、-8 までの任意の数値に対して機能するはずです。-9 の場合は 2*8 を追加します。

于 2014-03-17T00:23:12.867 に答える