模运算符(%)在C#中为不同的.NET版本提供了不同的结果
我正在encryption用户的input以生成密码的string。 但是一行代码在不同版本的框架中给出了不同的结果。 用户按键的部分代码:
按键:1.variablesascii
是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()
在两个版本中给出了正确的(相同的)结果。
原因是什么,有没有解决办法?
Math.Pow
处理双精度浮点数; 因此,您不应该期望比结果的前15-17位更准确:
所有浮点数的有效数字也是有限的,这也决定了一个浮点数值如何精确地逼近一个实数。
Double
值最多有15个十进制数字的精度,尽pipe内部最多保留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
除了你的哈希函数不是很好的事实之外,你的代码最大的问题不是它根据.NET的版本返回一个不同的数字,而是在两种情况下它都返回一个完全没有意义的数字:问题的正确答案是
49 103 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操作会寻求一个结果,要求每个数字都是正确的,包括最后一个数字。 因此,即使对于.NET 4中可能已经实现的Math.Pow
的精度的最小的改进,也会导致计算的巨大差异,这实质上会产生任意的结果。
*由于这个问题是关于在密码散列的情况下提高整数,所以在决定你现在的方法是否应该改变以获得更好的方法之前,阅读这个答案链接可能是一个非常好的主意。
你看到的是两倍的四舍五入误差。 Math.Pow
与double Math.Pow
工作,区别如下:
.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
之前的最后一个数字,并且导致结果的差异。 这不是模数运算符 (%)
。
浮点精度因机器而异, 甚至在同一台机器上也不相同 。
但是,.NET为您的应用程序创build一个虚拟机…但是从版本到版本都有变化。
所以你不应该依靠它来产生一致的结果。 对于encryption,使用框架提供的类而不是滚动你自己的类。
关于代码糟糕的方式有很多答案。 但是,至于为什么结果不同…
英特尔的FPU在内部使用80位格式来获得更高的中间结果精度。 所以如果一个值在处理器寄存器中,它会得到80位,但是当它被写入堆栈时,它将被存储在64位 。
我期望新版本的.NET在其即时(JIT)编译中有一个更好的优化器,所以它在寄存器中保留一个值,而不是写入堆栈,然后从堆栈中读回。
JIT可能现在可以在寄存器中而不是在堆栈中返回一个值。 或者将值传递给寄存器中的MOD函数。
另请参见堆栈溢出问题80位扩展精度数据types的应用程序/好处是什么?
其他处理器,例如ARM,将给这个代码不同的结果。
也许最好只用整数算术自己计算一下。 就像是:
int n = 143; int e = 103; int result = 1; int ascii = (int) 'a'; for (i = 0; i < e; ++i) result = result * ascii % n;
您可以将性能与其他答案中公布的BigInteger解决scheme的性能进行比较。