web-dev-qa-db-ja.com

モジュロを使用しているときにC ++が負の数値を出力するのはなぜですか?

数学

このような方程式がある場合:

_x = 3 mod 7
_

xは... -4、3、10、17、...、またはより一般的なものです。

_x = 3 + k * 7
_

ここで、kは任意の整数です。モジュロ演算が数学のために定義されていることは知りませんが、因子環は確かにそうです。

Python

Pythonでは、_%_を正のmと共に使用すると、常に負でない値を取得します。

_#!/usr/bin/python
# -*- coding: utf-8 -*-

m = 7

for i in xrange(-8, 10 + 1):
    print(i % 7)
_

結果:

_6    0    1    2    3    4    5    6    0    1    2    3    4    5    6    0    1    2    3
_

C++:

_#include <iostream>

using namespace std;

int main(){
    int m = 7;

    for(int i=-8; i <= 10; i++) {
        cout << (i % m) << endl;
    }

    return 0;
}
_

出力されます:

_-1    0    -6    -5    -4    -3    -2    -1    0    1    2    3    4    5    6    0    1    2    3    
_

ISO/IEC 14882:2003(E)-5.6乗法演算子:

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

そして

74)ISO Cの改訂に向けて進行中の作業によれば、整数除算の優先アルゴリズムは、ISO Fortran規格ISO/IEC 1539:1991で定義された規則に従います。この規則では、商は常にゼロに丸められます。

出典: ISO/IEC 14882:2003(E)

(_ISO/IEC 1539:1991_の無料版が見つかりませんでした。どこから入手できるか誰にもわかりませんか?)

操作は次のように定義されているようです:

enter image description here

質問

そのように定義するのは理にかなっていますか?

この仕様の議論は何ですか?そのような標準を作成する人々がそれについて議論する場所はありますか?彼らがこのようにすることを決めた理由について何かを読むことができますか?

ほとんどの場合、モジュロを使用すると、データ構造の要素にアクセスしたいです。この場合、modが負でない値を返すことを確認する必要があります。したがって、この場合、常に非負の値を返すmodを使用するとよいでしょう。 (別の使用法は ユークリッドアルゴリズム です。このアルゴリズムを使用する前に両方の数値を正にすることができるため、モジュロの符号が重要になります。)

追加資料

Moduloがさまざまな言語で実行する機能の長いリストについては、 Wikipedia を参照してください。

47
Martin Thoma

X86(および他のプロセッサアーキテクチャ)では、整数の除算とモジュロは単一の演算idiv(符号なしの値の場合はdiv)によって実行され、商と余りの両方を生成します(ワードサイズの場合) AXおよびDXの引数)。これは、Cライブラリ関数divmodで使用されます。この関数は、コンパイラーによって単一の命令に最適化できます!

整数除算では、次の2つの規則が考慮されます。

  • 整数でない商はゼロに丸められます。そして
  • 方程式 dividend = quotient*divisor + remainderは結果に満足しています。

したがって、負の数を正の数で除算すると、商は負(またはゼロ)になります。

そのため、この動作は一連のローカルな決定の結果として見ることができます。

  • プロセッサー命令セットの設計は、一般的ではないケース(モジュロ)よりも一般的なケース(分割)に対して最適化されます。
  • 一貫性(ゼロに向かって丸め、除算式を尊重する)は、数学的な正確さよりも優先されます。
  • Cは効率と単純さを好みます(特に、Cを「高レベルアセンブラ」と見なす傾向がある場合)。そして
  • C++はCとの互換性を好みます。
26
ecatmur

この仕様の議論は何ですか?

C++の設計目標の1つは、ハードウェアに効率的にマップすることです。基礎となるハードウェアが負の剰余を生成する方法で除算を実装する場合、C++で%を使用すると、それが得られます。それだけです。

そのような標準を作成する人々がそれについて議論する場所はありますか?

Comp.lang.c ++。moderatedと、それほどではないがcomp.lang.c ++で興味深い議論を見つけることができます。

10
fredoverflow

当時、x86命令セットの設計者は、整数の除算を切り捨てるのではなく、ゼロに丸めることが正しいと判断しました。 (数千匹のラクダのノミが母親のひげに巣を作るように。)数学的な正確さをある程度保つために、「残り」と発音されるREMオペレーターはそれに応じて行動しなければなりませんでした。これを読んではいけません: https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzatk/REM.htm

私はあなたに警告しました。後にC仕様を実行した誰かが、コンパイラが正しい方法またはx86の方法のいずれかでそれを実行することに準拠することを決定しました。その後、C++仕様を実行する委員会は、C方式で実行することを決定しました。その後、まだこの質問が投稿された後、C++委員会は間違った方法で標準化することを決定しました。今、私たちはそれにこだわっています。多くのプログラマーは、次のような関数を書いています。私はおそらく少なくとも十数回それをやったことがあります。

 inline int mod(int a, int b) {int ret = a%b; return ret>=0? ret: ret+b; }

効率が上がります。

最近は、基本的に次のものを使用し、type_traitsのものをいくつか投入しています(後日C++を使用して改善するアイデアを与えてくれたClearerに感謝します。以下を参照してください)。

<strike>template<class T>
inline T mod(T a, T b) {
    assert(b > 0);
    T ret = a%b;
    return (ret>=0)?(ret):(ret+b);
}</strike>

template<>
inline unsigned mod(unsigned a, unsigned b) {
    assert(b > 0);
    return a % b;
}

本当の事実:私はPascal標準化委員会に働きかけて、彼らが寛容になるまで正しい方法でMODを行うようにしました。私の恐ろしいことに、彼らは間違った方法で整数除算をしました。したがって、それらは一致しません。

編集:Clearerは私にアイデアを与えました。私は新しいものに取り組んでいます。

#include <type_traits>

template<class T1, class T2>
inline T1 mod(T1 a, T2 b) {
    assert(b > 0);
    T1 ret = a % b;
    if constexpr  ( std::is_unsigned_v<T1>)
    {
        return ret;
    } else {
        return (ret >= 0) ? (ret) : (ret + b);
    }
}
8
Jive Dadson