С#, операция по модулю дает другой результат, чем калькулятор

Итак, я хотел написать этот метод: 142 ^ 23 (мод 187), и с помощью любого калькулятора я получаю результат 65, но с этим фрагментом кода: double number = Math.Pow(142, 23) % 187 я получаю результат 53. Почему это так, и что я делаю неправильно здесь?


person John Smith    schedule 23.03.2018    source источник
comment
а о каком калькуляторе речь?   -  person Steve    schedule 23.03.2018
comment
@Steve любой калькулятор!   -  person itsme86    schedule 23.03.2018
comment
BigInteger.ModPow(142, 23, 187) дает мне 65. Я подозреваю, что число слишком велико, поэтому double приходится жертвовать точностью. Хотя я не знаю, как это доказать.   -  person    schedule 23.03.2018


Ответы (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 делает что-то более умное с двоичным возведением в степень, которое будет лучше работать с действительно огромным числом.

person vcsjones    schedule 23.03.2018

Если мы используем 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. Прошло много времени с тех пор, как я это сделал, поэтому я оставлю это в качестве упражнения для читателя :)

person Community    schedule 23.03.2018
comment
@vcsjones Я не мог полностью объяснить потерю точности в своем комментарии, поэтому у меня была миссия. это был забавный и сложный вопрос, чтобы ответить. Я все еще работаю над последними двумя битами. - person ; 23.03.2018
comment
Я почти уверен, что где-то ошибся. - person ; 23.03.2018