web-dev-qa-db-ja.com

モジュロ演算子(%)は、C#の.NETバージョンごとに異なる結果を返します

パスワードの文字列を生成するために、ユーザーの入力を暗号化しています。ただし、コードの行は、フレームワークのバージョンが異なると結果が異なります。ユーザーが押したキーの値を持つ部分的なコード:

キーが押されたとき:1.変数asciiは49です。計算後の 'e'および 'n'の値:

_e = 103, 
n = 143,

Math.Pow(ascii, e) % n
_

上記のコードの結果:

  • .NET 3.5(C#)で

    _Math.Pow(ascii, e) % n
    _

    _9.0_を返します。

  • .NET 4(C#)で

    _Math.Pow(ascii, e) % n
    _

    _77.0_を返します。

Math.Pow()は、両方のバージョンで正しい(同じ)結果を返します。

原因は何ですか?解決策はありますか?

89
Rajiv Bhardwaj

Math.Pow は、倍精度浮動小数点数で機能します。したがって、結果の 最初の15–17桁 以上が正確であると期待するべきではありません。

すべての浮動小数点数の有効桁数も限られているため、浮動小数点値が実数にどれだけ正確に近似するかが決まります。 Double値の精度は10進数で最大15桁ですが、内部で最大17桁が維持されます。

ただし、モジュロ演算では、すべての桁が正確である必要があります。あなたの場合、あなたは計算しています49103、その結果は175桁で構成され、両方の答えでモジュロ演算が無意味になります。

正しい値を算出するには、 BigInteger クラス(.NET 4.0で導入)で提供されている任意精度の算術演算を使用する必要があります。

int val = (int)(BigInteger.Pow(49, 103) % 143);   // gives 114

Edit:以下のコメントでMark Petersが指摘したように、 BigInteger.ModPow メソッド。これは、この種の操作専用です。

int val = (int)BigInteger.ModPow(49, 103, 143);   // gives 114
160
Douglas

ハッシュ関数はあまり良いものではないという事実は別として *コードの最大の問題は、.NETのバージョンに応じて異なる数値を返すことではなく、どちらの場合もまったく意味のない数値を返すことです。問題に対する正しい答えは

49103 mod 143 =は114です。( Wolfram Alphaへのリンク

この答えを計算するのにこのコードを使用できます:

private static int PowMod(int a, int b, int mod) {
    if (b == 0) {
        return 1;
    }
    var tmp = PowMod(a, b/2, mod);
    tmp *= tmp;
    if (b%2 != 0) {
        tmp *= a;
    }
    return tmp%mod;
}

計算が異なる結果を生成する理由は、答えを生成するために、49の有効数字のほとんどをドロップする中間値を使用するためです。103 番号:175桁の最初の16桁のみが正しい!

1230824813134842807283798520430636310264067713738977819859474030746648511411697029659004340261471771152928833391663821316264359104254030819694748088798262075483562075061997649

残りの159桁はすべて間違っています。ただし、mod操作は、最後の桁も含めて、すべての数字が正しいことを必要とする結果を探します。したがって、Math.Pow .NET 4で実装されている可能性があるため、計算に大きな違いが生じ、本質的に任意の結果が生成されます。

* この質問は、パスワードハッシュのコンテキストで整数を高出力にすることについて説明しているため、 この答えは非常に良い考えかもしれませんリンク 現在のアプローチをより良いものに変更する必要があるかどうかを決定する前に。

72
dasblinkenlight

表示されるのは、倍精度の丸め誤差です。 _Math.Pow_はdoubleで機能し、違いは以下のとおりです。

.NET 2.0および3.5 => var powerResult = Math.Pow(ascii, e);は以下を返します。

_1.2308248131348429E+174
_

.NET 4.0および4.5 => var powerResult = Math.Pow(ascii, e);は以下を返します。

_1.2308248131348427E+174
_

Eの前の最後の数字に注意してください。これが結果に違いを引き起こしています。 モジュラス演算子ではありません _(%)_。

27
Habib

浮動小数点の精度はマシンごとに異なる場合があり、 同じマシンであっても です。

ただし、.NETはアプリ用の仮想マシンを作成しますが、バージョンごとに変更があります。

したがって、一貫した結果を得るためにそれに頼るべきではありません。暗号化には、独自のロールではなく、フレームワークが提供するクラスを使用します。

24
Joe

コードが悪い方法について多くの答えがあります。ただし、結果が異なる理由については…

Intelの FP 内部で80-bit形式を使用して、中間結果の精度を高めます。したがって、プロセッサレジスタに値がある場合は80ビットを取得しますが、スタックに書き込まれると、64ビットに格納されます

新しいバージョンの.NETは、ジャストインタイム(JIT)コンパイルでより優れたオプティマイザーを備えていると予想されるため、スタックに値を書き込んでからスタックから値を読み取るのではなく、レジスタに値を保持しています。

JITがスタックではなくレジスタに値を返すようになった可能性があります。または、レジスタのMOD関数に値を渡します。

Stack Overflowの質問も参照してください80ビット拡張精度データ型のアプリケーション/利点は何ですか?

他のプロセッサ、たとえばARMは、このコードに対して異なる結果を提供します。

10
Ian Ringrose

整数演算のみを使用して自分で計算するのが最善かもしれません。何かのようなもの:

int n = 143;
int e = 103;
int result = 1;
int ascii = (int) 'a';

for (i = 0; i < e; ++i) 
    result = result * ascii % n;

他の回答に掲載されているBigIntegerソリューションのパフォーマンスとパフォーマンスを比較できます。

6
Ronald