模运算符(%)为C#中的不同.N​​ET版本提供了不同的结果

时间:2014-01-31 13:53:03

标签: c# .net modulo

我正在加密用户输入以生成密码字符串。但是一行代码在不同版本的框架中给出了不同的结果。用户按下键值的部分代码:

按下键: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()在两个版本中都给出了正确的(相同的)结果。

原因是什么,是否有解决方案?

6 个答案:

答案 0 :(得分:160)

Math.Pow适用于双精度浮点数;因此,您不应期望结果的first 15–17 digits以上更准确:

  

所有浮点数也具有有限的有效位数,这也决定了浮点值接近实数的精确程度。 Double值的精度最多为十进制数15位,但内部最多保留17位数。

但是,模运算要求所有数字都是准确的。在你的情况下,你正在计算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

答案 1 :(得分:72)

除了你的散列函数不是很好的 * 之外,你的代码最大的问题并不是它根据.NET的版本返回不同的数字,而是在这两种情况下,它都会返回一个完全没有意义的数字:问题的正确答案是

49 103 mod 143 =是114.(link to 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 数字的大多数有效数字:只有前16个其175位数字是正确的!

1230824813134842807283798520430636310264067713738977819859474030746648511411697029659004340261471771152928833391663821316264359104254030819694748088798262075483562075061997649

其余的159位数字都错了。然而,mod操作寻求一个结果,要求每个数字都是正确的,包括最后一个数字。因此,即使是对.NET 4中可能已实现的Math.Pow精度的最微小的改进,也会导致计算的巨大差异,从而产生任意结果。

* 由于这个问题涉及在密码散列的情况下将整数提升到高权,所以在决定你当前的方法是否应该是之前阅读this answerlink可能是一个非常好的主意。改变了可能更好的一个。

答案 2 :(得分:27)

你看到的是双倍的舍入错误。 Math.Pow使用double,差异如下:

.NET 2.0和3.5 =&gt; var powerResult = Math.Pow(ascii, e);返回:

1.2308248131348429E+174

.NET 4.0和4.5 =&gt; var powerResult = Math.Pow(ascii, e);返回:

1.2308248131348427E+174

注意E之前的最后一位数字,这会导致结果出现差异。 这不是模数运算符 (%)

答案 3 :(得分:24)

浮点精度因机器而异,even on the same machine

  

但是,.NET为您的应用程序创建了一个虚拟机......但版本之间存在相应的变化。

因此,您不应该依赖它来产生一致的结果。对于加密,请使用Framework提供的类,而不是自己编译。

答案 4 :(得分:10)

关于代码坏的方式有很多答案。但是,为什么结果不同......

英特尔FPUs在内部使用 80位格式,以获得更高的中间结果精度。因此,如果处理器寄存器中的值为80位,但当它写入堆栈时,它将以 64位存储。

我希望新版本的.NET在其即时(JIT)编译中有一个更好的优化器,因此它在寄存器中保留一个值而不是将其写入堆栈然后从堆栈中读取它

可能JIT现在可以在寄存器中而不是在堆栈上返回值。或者将值传递给寄存器中的MOD函数。

另请参阅Stack Overflow问题 What are the applications/benefits of an 80-bit extended precision data type?

其他处理器,例如ARM将为此代码提供不同的结果。

答案 5 :(得分: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解决方案的性能进行比较。