Математически, почему этот алгоритм SICP для экспоненты числа по модулю другого числа работает?

Раздел 1.2.6 SICP дает следующую процедуру:

    (define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expmod base (/ exp 2) m))
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))

Авторы утверждают, что он вычисляет экспоненту числа по модулю другого числа. Например, (expmod 5 3 n) должен возвращать (5^3) по модулю n.

Однако с математической точки зрения я просто не понимаю, как это работает. Согласно сноске 46, предполагается использовать свойство, состоящее в том, что для любых положительных целых чисел a, b и n (ab) mod n = [(a mod n)(b mod n)] mod n, но мне не удается посмотреть, как он на самом деле использует его. Рассмотрим (expmod 5 3 3):

  1. Сначала мы вызываем (expmod 5 3 3). Математически это означает, что мы запрашиваем (5^3) по модулю 3.
  2. Поскольку второй параметр нечетный, мы вычисляем (remainder (* 5 (expmod 5 (- 3 1) 3)) 3), т. е. (remainder (* 5 (expmod 5 2 3)) 3). Математически это [5 * [(5^2) по модулю 3]] по модулю 3. Поскольку к начальному числу 5 в этом выражении не присоединен модуль по модулю 3, это выражение не находится в (ab ) mod n = [(a mod n)(b mod n)] mod n form, поэтому не удается использовать предполагаемое свойство.

Итак, учитывая, что это, похоже, не использует предполагаемое свойство, почему этот алгоритм работает? Какое свойство модульной арифметики я упустил из виду?


person J. Mini    schedule 04.11.2020    source источник
comment
Что касается пункта 2, обратите внимание, что (a * (b mod n)) mod n также равно (a * b) mod n.   -  person molbdnilo    schedule 04.11.2020
comment
@molbdnilo Странно, если бы что-то настолько приятное было правдой, я бы ожидал, что это будет в центре внимания список свойств Википедии. Я пропустил это?   -  person J. Mini    schedule 04.11.2020
comment
Оно тривиально следует из правила сложения. (Большинство вещей, которые стоит знать, вообще не упоминаются в Википедии.)   -  person molbdnilo    schedule 04.11.2020
comment
@molbdnilo Из правила сложения? Если это тривиально, я не могу увидеть это так легко.   -  person J. Mini    schedule 09.11.2020


Ответы (2)


Это определение fast-exp из 1.2.4, которое относится к:

(define (fast-expt b n)
  (cond ((= n 0) 1)
        ((even? n) (square (fast-expt b (/ n 2))))
        (else (* b (fast-expt b (- n 1))))))

Если мы переименуем вещи, чтобы они больше соответствовали expmod, это будет выглядеть так:

(define (expt base exp)
  (cond ((= exp 0) 1)
        ((even? exp)
         (square (expt base (/ exp 2))))
        (else
         (* base (expt base (- exp 1))))))

Чтобы получить наивное expmod, мы можем пока просто вычислить остаток от каждого предложения:

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expt base (/ exp 2))) m))
        (else
         (remainder (* base (expt base (- exp 1))) m))

До сих пор мы не использовали сноску (ab) mod m = ((a mod m)(b mod m) mod m). Конечно, это частный случай (aa) mod m = ((a mod m)(a mod m) mod m), который дает (remainder (square a) m) = (remainder (sqaure (remainder a m)) m). Мы можем использовать это с предложением even, чтобы

         (remainder (square (expt base (/ exp 2))) m)

становится:

         (remainder (square (remainder (expt base (/ exp 2)) m))
                    m)

В середине у нас есть остаток от экспоненты, так что это эквивалентно:

         (remainder (square (expmod base (/ exp 2) m)) m)

Используя новое предложение even, мы имеем

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expmod base (/ exp 2) m)) 
                    m))
        (else
         (remainder (* base (expt base (- exp 1))) m))

Чтобы упростить нечетное предложение, давайте пока используем E вместо (expt base (- exp 1)).

Используя определяющие свойства числа mod, мы можем сказать для любого числа a:

         a = (+ (* (quotient a m) m) (remainder a m))

Так что верно и то, что:

         E = (+ (* (quotient E m) m) (remainder E m))

подставив это в наше предложение odd:

         (remainder (* base E) m)

дает:

         (remainder (* base (+ (* (quotient E m) m) (remainder E m))) m)

Мы можем игнорировать (* (quotient E m) m), потому что любой терм, содержащий это, делится на m и поэтому будет оцениваться как 0 при выполнении внешнего remainder, так что это эквивалентно:

         (remainder (* base (remainder E m)) m)

расширение E до исходного значения:

         (remainder (* base (remainder (expt base (- exp 1)) m)) m)

еще раз, в середине у нас есть остаток от экспоненты, так что это становится:

         (remainder (* base (expmod base (- exp 1) m)) m)

И наш expmod теперь:

(define (expmod base exp m)
  (cond ((= exp 0) 1)
        ((even? exp)
         (remainder (square (expmod base (/ exp 2) m)) 
                    m))
        (else
         (remainder (* base (expmod base (- exp 1) m))
                    m))))
person codybartfast    schedule 04.11.2020
comment
Итак, в конечном счете, нечетная ветвь не использует рассматриваемое свойство? - person J. Mini; 04.11.2020
comment
(remainder (* base (remainder E m)) m) выглядит как [a*(b mod m)] mod m. Поскольку это было получено из (remainder (* base E) m), правильно ли я заключаю, что вы дали доказательство того же свойства, что и Sorawee Porncharoenwase? то есть (ab) mod n = [a (b mod n)] mod n? - person J. Mini; 04.11.2020
comment
@ J.Mini Правильно, я не использовал это в нечетном предложении. Возможно, есть способ использовать его при устранении (* (quotient E m) m), но я недостаточно умен, чтобы увидеть это, и в любом случае это кажется немного надуманным. Я не внимательно смотрел на ответ Сорави, был занят написанием своего собственного :-), возможно, я бы сказал, что этот ответ основан на его ответе? - person codybartfast; 05.11.2020

(ab) mod n = [a (b mod n)] mod n

также верно.

Вот доказательство по индукции по a.

Базовый случай: когда a = 0, (0b) mod n = 0 mod n = [0 (b mod n)] mod n.

Индуктивный случай:

По предположению индукции предположим, что (ab) mod n = [a (b mod n)] mod n верно. Нам нужно доказать, что ((a+1) b) mod n = [(a + 1) (b mod n)] mod n.

((a+1) b) mod n
= (ab + b) mod n
= (ab mod n) + (b mod n)
= [a (b mod n)] mod n + (b mod n)             by induction hypothesis
= [a (b mod n)] mod n + (b mod n) mod n
= [a (b mod n) + (b mod n)] mod n
= [(a + 1) (b mod n)] mod n

по желанию.

На этом заканчивается доказательство того, что

(ab) mod n = [a (b mod n)] mod n

На самом деле, вы можете видеть, что

(ab) mod n = [(a mod n) (b mod n)] mod n

является следствием, вытекающим из него. Вот доказательство:

(ab) mod n 
= [a (b mod n)] mod n            by what we just proved
= [(b mod n) a] mod n
= [(b mod n) (a mod n)] mod n    by what we just proved
= [(a mod n) (b mod n)] mod n
person Sorawee Porncharoenwase    schedule 04.11.2020
comment
Я удивлен, что этого не было в Википедии. Версия, которую вы доказали, сильнее той, которую они приводят. Зачем приводить конкретный случай, когда есть более мощная версия, не требующая дополнительных понятий? Более того, я удивлен, что этого нет в сноске SICP. Это заставляет меня задаться вопросом, не прочитал ли я это неправильно. Я? - person J. Mini; 04.11.2020
comment
Что ж, Википедия не содержит всего. Кроме того, в [(a mod n) (b mod n)] mod n нет ничего плохого. Кто-то может возразить, что ошибка SICP в том, что они неточно следовали уравнению, и исправление заключается в использовании (* (remainder base m) ...) вместо (* base ...). На самом деле, с точки зрения производительности, лучше использовать (* (remainder base m) ...). См., например, мой PR, чтобы улучшить производительность modular-expt в Racket. - person Sorawee Porncharoenwase; 05.11.2020