Итак, я хотел написать этот метод: 142 ^ 23 (мод 187), и с помощью любого калькулятора я получаю результат 65, но с этим фрагментом кода: double number = Math.Pow(142, 23) % 187
я получаю результат 53. Почему это так, и что я делаю неправильно здесь?
С#, операция по модулю дает другой результат, чем калькулятор
Ответы (2)
Math.Pow(142, 23)
слишком велик, чтобы его можно было точно представить двойником. Таким образом, ваш модуль выполняется с потерями.
Это даст правильный ответ:
BigInteger.ModPow(142, 23, 187);
BigInteger
можно найти в пространстве имен и сборке System.Numerics
.
Вы также можете эффективно реализовать это самостоятельно, если хотите, чтобы целые числа того размера, который вы использовали в своем вопросе.
private static int ModPow(int basenum, int exponent, int modulus)
{
if (modulus == 1)
{
return 0;
}
int result = 1;
for (var i = 0; i < exponent; i++)
{
result = (result * basenum) % modulus;
}
return result;
}
BigInteger
делает что-то более умное с двоичным возведением в степень, которое будет лучше работать с действительно огромным числом.
Если мы используем BigInteger
для вычисления полного результата экспоненты:
var bi = BigInteger.Pow(142, 23);
Debug.WriteLine(bi);
Получаем очень большое число:
31814999504641997296916177121902819369397243609088
or
3.1814999504642E+49
Если мы затем преобразуем это значение в двойное, чтобы вызвать потерю точности, а затем обратно в BigInteger
:
var d = (double) bi;
bi = new BigInteger(d);
Debug.WriteLine(bi);
Мы получаем:
31814999504641997296916177121902819369397243609088 -- BigInteger
31814999504641993108158684988768059669621048868864 -- BigInteger -> double -> BigInteger
^ oh no mah precision
В шестнадцатеричном формате, где потеря точности более очевидна:
15C4 C9EB 18CD 25CE 858D 6C2D C3E5 D319 BC9B 8000 00
15C4 C9EB 18CD 2500 0000 0000 0000 0000 0000 0000 00
^ oh no mah precision
Вы заметите, что потеря точности происходит на 17-й десятичной цифре или 14-й шестнадцатеричной цифре.
Почему?
Значок double
хранится с использованием кодировки IEEE-754:
Significand or mantissa: 0-51
Exponent: 52-62
Sign (0 = Positive, 1 = Negative) 63
Ключ здесь — 52 бита для мантиссы. Наши 14 шестнадцатеричных цифр составляют 56 бит, что близко к 52-битному пределу. Как мы можем объяснить 4-битное несоответствие?
(Думаю, я допустил ошибку в последующем объяснении. Если кто-то может указать на это, я был бы признателен)
Последняя неизменная шестнадцатеричная цифра — C
или 1100
в двоичном формате; поскольку последние два бита — нули, наше число было закодировано 54 битами, а не 56. Таким образом, на самом деле это 2-битное несоответствие.
Как мы учитываем последние два бита? Это связано с тем, как определяется дробная составляющая IEEE-754. Прошло много времени с тех пор, как я это сделал, поэтому я оставлю это в качестве упражнения для читателя :)
BigInteger.ModPow(142, 23, 187)
дает мне 65. Я подозреваю, что число слишком велико, поэтомуdouble
приходится жертвовать точностью. Хотя я не знаю, как это доказать. - person   schedule 23.03.2018