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


89

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

押されたキー: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() 両方のバージョンで正しい(同じ)結果が得られます。

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


12
もちろん、質問の両方の答えは間違っています。あなたがそれを気にしていないように見えるという事実は、まあ、心配です。
David Heffernan 2014年

34
いくつかのステップに戻る必要があります。「パスワードの文字列を生成するためにユーザーの入力を暗号化しています」この部分はすでに疑わしいものです。実際に何をしたいですか?パスワードを暗号化またはハッシュ形式で保存しますか?これをエントロピーとして使用して、ランダムな値を生成しますか?セキュリティの目標は何ですか?
CodesInChaos 2014年

49
この質問は浮動小数点演算の興味深い問題を示していますが、OPの目標が「パスワードの文字列を生成するためのユーザー入力を暗号化すること」である場合は、独自の暗号化をロールすることはお勧めできないため、お勧めしません。実際に答えを実装します。
Harrison Paine

18
他の言語%が浮動小数点数での使用を禁止する理由を示しています。
Ben Voigt 2014年

5
答えは良いものですが、.NET 3.5と4の間で何が変更され、異なる動作を引き起こしているのかという質問には答えません。
msell 2014

回答:


160

Math.Pow倍精度浮動小数点数で機能します。したがって、最初の15〜17桁を超えることはできません。結果のが正確であると。

また、すべての浮動小数点数の有効桁数には制限があり、浮動小数点値が実数にどれだけ正確に近似するかも決定されます。あDouble17桁の最大値が内部に維持されるものの値は、精度の15桁まで有します。

ただし、モジュロ演算では、すべての桁が正確である必要があります。あなたの場合、あなたは49を計算しています 103、結果は175桁で構成されるているので、モジュロ演算はどちらの答えでも意味。

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

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

編集:以下のコメントでMark Petersが指摘しBigInteger.ModPowているように、この種の操作専用のメソッドを使用する必要があります。

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

20
真の問題、つまり問題のコードが明らかに間違っていることを指摘するための+1
David Heffernan 14年

36
BigIntegerが提供するModPow()メソッドは、この操作で(今の私のクイックテストでは)約5倍高速に実行できることに注意してください。
Mark Peters

8
+1編集あり。ModPowは高速であるだけでなく、数値的に安定しています!
レイ

2
@メーカーいいえ、答えは無効でなく意味がありません。
コーディグレイ

3
@ makerofthings7:私は原則的にあなたに同意します。ただし、不正確さは浮動小数点演算に固有のものであり、一般的な操作に制限を課すよりも、開発者がリスクを認識することを期待する方が現実的であると考えられます。本当に「安全」であることが望まれる場合、言語はまた、への1.0 - 0.9 - 0.1 == 0.0評価などの予期しない結果を回避するために、浮動小数点の等価比較を禁止する必要がありfalseます。
ダグラス

72

あなたのハッシュ関数があまり良くないという事実は別として*、あなたのコードの最大の問題は、それが.NETのバージョンに応じて、異なる数を返すということではなく、両方のケースでは、それは完全に無意味な数値を返すこと:問題に対する正しい答えは

49 103 MOD 143 =は114(あるウルフラムアルファへのリンク

このコードを使用して、この答えを計算できます。

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に実装されている可能性がある精度のほんのわずかな改善でも、計算に大幅な違いが生じ、本質的に任意の結果が生成されます。

*この質問は整数をパスワードのハッシュのコンテキストで累乗することについて話しているので、読むことは非常に良い考えかもしれません、現在のアプローチを潜在的に優れたものに変更する必要があるかどうかを判断する前に、この回答リンクです。


20
いい答えだ。本当のポイントは、これはひどいハッシュ関数であるということです。OPはソリューションを再考し、より適切なアルゴリズムを使用する必要があります。
david.pfx 2014年

1
アイザック・ニュートン:リンゴが地球に引き寄せられるのと同じ方法で月が地球に引き寄せられる可能性はありますか?@ david.pfx:本当のポイントは、これはリンゴを選ぶためのひどい方法だということです。ニュートンは解決策を再考し、おそらくはしごを持つ男を雇う必要があります。
jwg 2014

2
@jwg Davidのコメントには、理由のために多くの賛成投票がありました。元の質問では、アルゴリズムがパスワードのハッシュに使用されていたことが明らかになりました。これは確かにその目的のためのひどいアルゴリズムです。すでに実証されているように、.NETフレームワークのバージョン間で中断する可能性が非常に高いです。OPがアルゴリズムを「修正」するのではなく置き換える必要があることを述べていない答えは、OPが彼に害を及ぼすことになります。
Chris

@Chrisコメントをありがとう、Davidの提案を含めるように編集しました。OPのシステムはおもちゃか、彼が自分の娯楽のために作成する使い捨てのコードの一部である可能性があるため、私はあなたほど強くは言いませんでした。ありがとう!
dasblinkenlight 14

27

表示されるのは、2倍の丸め誤差です。 Math.Powdoubleで動作し、違いは次のとおりです。

.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。これが結果に違いを引き起こしています。これは係数演算子ではありません (%)


3
聖なる牛はこれがOPの質問に対する唯一の答えですか?私はすべてのメタ "何とかセキュリティの間違った質問私はあなたよりもn00bを知っています"をまだ読んでいて、「なぜ3.5と4.0の間の一貫した不一致がなぜあるか疑問に思いましたか? 「これは?」「あなたの本当の問題はあなたの足を見ていない」または「夜に自家製のサンダルを履いたときに何を期待しますか?!!!」と言われるだけです。ありがとう!
Michael Paulukonis

1
@MichaelPaulukonis:それは間違った例えです。岩石の研究は正当な追求です。固定精度のデータ型を使用して任意精度の演算を実行することは、明らかに間違っています。これを、C#を書くときに犬が猫よりも悪い理由を尋ねるソフトウェア採用担当者と比較します。あなたが動物学者であれば、この質問にはいくつかのメリットがあるかもしれません。他のすべての人にとって、それは無意味です。
ダグラス

24

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

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

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


10

コードが悪い方法については多くの答えがあります。しかし、なぜ結果が違うのか…

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

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

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

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

ARMなどの他のプロセッサでは、このコードに対して異なる結果が得られます。


6

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

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

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

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


7
それには、103の乗算と係数の削減が必要です。e2 = e * e%n、e4 = e2 * e2%n、e8 = e4 * e4%nなどを計算して、結果= e * e2%n * e4%n * e32%n * e64%n。合計11の乗算と係数の削減。関連する数字の大きさを考えると、1はさらにいくつかの係数削減を排除することができるが、それは11に103回の操作を減らすに比べてマイナーだろう
supercat

2
@supercat素敵な数学ですが、実際には、これをトースターで実行している場合にのみ関係があります。
alextgordon 2014年

7
@alextgordon:または、より大きな指数値の使用を計画している場合。指数値をたとえば65521に拡張すると、強度の削減を使用する場合は約28の乗算と係数の削減が行われ、使用しない場合は65,520が使用されます。
スーパーキャット2014年

+1は、計算がどのように行われるかが明確である、アクセス可能なソリューションを提供します。
jwg 14

2
@Supercat:あなたは絶対的に正しいです。アルゴリズムを改善するのは簡単です。これは、アルゴリズムが非常に頻繁に計算されるか、指数が大きい場合に関連します。ただし、主なメッセージは、整数演算を使用して計算できること、および計算する必要があることです。
ロナルド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.