Причина проблемы заключается в том, что для отрицательных дивидендов a
и / или делителей n
математическое определение операции по модулю обычно может отличаться от определения в соответствующем языке программирования.
Результат операции по модулю математически определяется как остаток от евклидова деления .Этот остаток всегда больше или равен нулю.Для положительных делителей n
остаток задается следующим образом:
a mod n = a - n * floor(a/n)
, где floor(a/n)
- это floor
-функция , которая дает на выходе наибольшее целое число, меньшее или равноеего вход (я не рассматриваю отрицательные делители n
, поскольку n = 128 > 0
в вопросе).
Примеры положительного делителя n = 128
с положительным дивидендом a = 559
(вверху) и отрицательного дивиденда a = -559
(внизу):
559 mod 128 = 559 - 128 * floor(559/128) = 559 - 128 * floor(4.37) = 559 -128 * 4 = 47
-559 mod 128 = -559 - 128 * floor(-559/128) = -559 - 128 * floor(-4.37) = -559 -128 * (-5) = 81
Однако во многих языках программирования(включая Java) используется другое определение для операции по модулю (иногда упоминается как симметричный вариант):
a mod n = a - n * trunc(a/n)
Здесь trunc(a/n)
означает усеченное деление, где частное a/n
округлено в сторонунуль.
Пример положительного делителя n = 128
с положительным дивидендом a = 559
(вверху) и отрицательным дивидендом a = -559
(внизу):
559 mod 128 = 559 - 128 * trunc(559/128) = 559 - 128 * trunc(4.37) = 559 -128 * 4 = 47
-559 mod 128 = -559 - 128 * trunc(-559/128) = -559 - 128 * trunc(-4.37) = -559 -128 * (-4) = -47
Как видно, обаопределения, математические и симметричные, дают разные результаты для отрицательных дивидендов.
Непосредственной причиной проблемы является то, что в формуле аффинного шифра подразумевается математическое определение, тогда как вВ вашем коде используется симметричный вариант (потому что Java использует симметричный вариант).Это хорошо видно на примере буквы Q
: в вашем коде буква Q
(= 81 dec
) зашифрована (A = 3
, B = 15
, M = 128
) до
(3 * 81 + 15) % 128 = 2
Расшифровка (A = 43
, B = 15
, M = 128
) равна
(43 * (2 - 15)) % 128 = -559 % 128
В зависимости от варианта по модулю результат равен -47
и 81
для симметричного и математическоговариант соответственно (см. выше).Поскольку в коде используется симметричный вариант, вы получите «неправильный» результат -47
.
Таким образом, решение состоит в том, чтобы переключить операцию по модулю в вашем коде на математическое определение.Это может быть достигнуто следующей формулой:
a mMod n = ((a sMod n) + n) sMod n
, где mMod
и sMod
обозначают математический и симметричный оператор по модулю соответственно.Для этого определите новый метод:
private static int mathematicalMod(int a, int n) {
return ((a % n) + n) % n;
}
и замените affineEncryption
-метод
encryption.append((char)((A * s.charAt(i) + B) % M));
на
encryption.append((char)mathematicalMod(A * s.charAt(i) + B, M));
и affineDecryption
-method
decryption.append((char)((A * Math.abs((s.charAt(i) - B))) % M));
с
decryption.append((char)mathematicalMod(A * (s.charAt(i) - B), M));
Обратите внимание, что при последней замене также удаляется Math.abs
-метод, поскольку он не принадлежит аффинности -cipher-decrypt-алгоритм .
С этими изменениями и следующим вводом
StringBuilder s = new StringBuilder("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0=1!2\"34$5%6&7/8(9)^`+*#'-_.:,;<>\\[]~{}|@");
вывод в консоль становится:
В Java также существует реализация симметричного варианта: int Math.floorMod(int a, int n)
, который, конечно же, также можно использовать вместо пользовательской реализации int mathematicalMod(int a, int n)
.