Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Возведение в степень по модулю. Что не так? (https://javascript.ru/forum/project/78546-vozvedenie-v-stepen-po-modulyu-chto-ne-tak.html)

Добрый_Серый_Волк 30.09.2019 20:49

Возведение в степень по модулю. Что не так?
 
Здравствуйте, гениальный математик, просматривающий тему.
Помогите починить мою бедную, сломанную голову и дайте совет что может быть не так и как должно быть ТАК.
Имеем набор функций, собранных из разных уголков интернета(изложенных мной) и предназначенных для возведения большого числа в большую степень по модулю:
/*1 - способ "справа налево. Представляем степень в виде двоичного числа и в зависимости от нуля или единицы творим всякую фигню."*/
     const PowToModFromWiki = (base, pow, mod) =>{
                let step = pow.toString(2).split('').map(Number);
                let result = 1;
                for(let i=step.length-1;i>0;i--){
                      result =(result * base**step[i])%mod;
                      base = (base**2)%mod;
                }
                return (result*base)%mod;
            }
            
   /*2 - способ упрощение степени. Возводим базу в квадрат, делим степень пополам*/ 
      const anotherPowToMod = (base, pow, mod) => {
                let temp = 1;
                while (pow>2){
                    if (!(pow%2)) {
                        base = base**2%mod;
                        pow/=2;
                    }
                    else {
                        pow--;
                        temp =(temp * base)%mod;
                    }
                }
                return (temp * (base**pow % mod))%mod;
            }
            
  /*3 - рекурсивное быстрое возведение в степень. Ненавижу рекурсию*/     
        const recursePowToMod = (base, pow, mod) => {
                const bynPow = (base, pow) =>{
                    if (pow == 1) return base;  
                    if (pow%2 == 0) {
                    let temp = bynPow(base, pow/2);
                    return temp**2%mod;
                        } else return bynPow(base, pow-1)*base%mod;
                }
                return bynPow (base, pow); 
            }
            
   /*4 - школьный метод, самый простой. Без комментариев.*/    
     const tooDumbPowToMod = (base, pow, mod) => {
                let result = 1;
                for(let i=0; i<pow; i++) {
                    result = result * base % mod;
                }
                return result;
            }

И все бы хорошо - при малых значениях базы, степени и модуля они возвращают одинаковое (правильное) значение.
Но, с ростом вводимых значений они начинают чудить: 1 и 2 возвращают одно значение 3 иногда как 1 и 2, а иногда свое; 4 - держится дольше всех, на 9-10 значных числах, ломаясь.
Вот, к примеру входные значения:366142356 1447804911 219020071
(вероятно)Правильный ответ - 38504623

рони 30.09.2019 21:32

Добрый_Серый_Волк,
искать библиотеку которая поддерживает большие числа типа Big Number.

Senseye 09.10.2019 00:23

Возможно добавят тут learn.javascript.ru BigInt
А пока, есть тип BigInt, поддерживает перемножение

Aetae 09.10.2019 00:37

Senseye, осёл и яблоко не умеют.
А вообще бесит что нельзя смешивать BigInt и Number - это против всей парадигмы языка идёт, в котором можно было любые операции к чему угодно применять не боясь словить Exception. Возвращали бы BigInt в итоге, или, если так принципиально - NaN.

Senseye 09.10.2019 00:48

Приводиш в нужные типы и все
Number(BigInt(100) ** BigInt(2))

А по поводу Exception, нужно писать тесты

Aetae 09.10.2019 01:29

Тесты это прекрасно, но изменение основной логики при введении нового типа и только для этого типа - это преступлении против языка.
Я мог умножить что угодно на что угодно, хоть Number хоть Object хоть чёрта в ступе, и продолжить работу со своим NaN. Но тут вылезает BigInt, который умножается только на BigInt да ещё и кидает exception. Это позволительно для сторонней либы, но никак не для нового примитивного типа.

Добрый_Серый_Волк 17.10.2019 22:00

Спасибо, дорогие товарищи, за ответы. Читал про BigInt и пробовал с этим типом, но что-то не пошло. Видать не умею их готовить.
Проблему потери точности(ну и задачу) решил написанием и добавлением двух простых алгоритмов: умножения(по Трахтенбергу) и вычитания (заменил им деление по модулю) чисел в их строковом представлении.

Malleys 18.10.2019 18:03

Цитата:

Сообщение от Добрый_Серый_Волк
Вот, к примеру входные значения:366142356 1447804911 219020071
(вероятно)Правильный ответ - 38504623

Действительно правильный!
function powToMod(base, power, module) {
	if(power === 1n) return base;
	if(power % 2n === 0n) return powToMod(base, power / 2n, module) ** 2n % module;
	return powToMod(base, power - 1n, module) * base % module;
}

alert(powToMod(366142356n, 1447804911n, 219020071n));


BigInt не является частью спецификации ECMASCRIPT 2019, черновик смотрите на https://tc39.es/proposal-bigint/ Данный ответ написан с использованием данного черновика, возможно кардинальное изменение в логике. Не стоит сейчас это использовать в браузере, но думаю вы можете использовать в node.js, поскольку управляемое вами окружение! https://caniuse.com/#feat=bigint

Цитата:

Сообщение от Aetae
бесит что нельзя смешивать BigInt и Number - это против всей парадигмы языка идёт

А как такое смешение должно работать? Например, поделим 5 на 2n. Какой результат должен получиться? 2.5 или 2n или NaN, как вы предлагаете? На самом деле математическое значение 2.5.

Семантика всех операторов в идеале должна основываться на некоторых математических принципах, чтобы соответствовать ожиданиям разработчиков. Операторы деления и нахождения остатка основаны на соглашениях из других языков программирования для целых чисел.

Тип Number может представить только математические значения c точностью в 15—17 десятичных цифр и масштабы в диапазоне примерно от 10⁻³⁰⁸ до 10³⁰⁸. Т. е. он представляет конечное число чисел. Это конечное множество чисел.

Тип BigInt представляет целочисленные математические значения. Т. е. этот тип представляет бесконечное число чисел. Это множество целых чисел.

Т. е. получается, что тип BigInt в основном представляет бесконечное кол-во чисел, которые не могут быть выражены через тип Number.

Цитата:

Сообщение от Aetae
или, если так принципиально - NaN

Определённо не NaN, поскольку результатом является математическое значение 2.5.

Цитата:

Сообщение от Aetae
Возвращали бы BigInt в итоге

Лучше всего в данном случае выкинуть исключение о неправильных типах, поскольку и тип Number содержит числа, не приводимые к типу BigInt, и тип BigInt содержит числа, не приводимые к типу Number.

Цитата:

Сообщение от Aetae
можно было любые операции к чему угодно применять не боясь словить Exception

try {
    alert("Symbol: " + Symbol.iterator);
} catch(error) {
    alert("Это не ECMAScript1, приведите к нужному типу");
}
Однако поймали!

В ECMAScript1 не было блока try-catch, так что вы бы не смогли поймать исключение при умножении {} * {}, поэтому вам выдают до сих пор (из-за обратной совместимости) NaN в таких случаях. Кстати при сложении строк и массивов определённой длины тоже может произойти исключение.

Продемонстрирую на примере массива
try {
    const a = new Array(2 ** 32 - 1);
    const b = [1, 2, 3];
    const c = a.concat(b);
    alert(c.length);
} catch(error) {
    alert("Не складывается");
}


UPD
Так как тогда 5 поделить на 2? Используйте либо тип Number, либо тип BigInt.

alert(5n / 2n);

alert(5 / 2);


UPD
#funfact Двоичное, восьмеричное и шестнадцатеричное представление целых чисел...
alert(0xffffffffffffffffffffffffffffffffffffffffffn);

alert(0b111111111111111111111111111111111111111111n);

alert(0o777777777777777777777777777777777777777777n);


UPD
Возможно многие вопросы отпадут, если вы подумаете, что тип называется именно BigInt, а не BigDecimal.

UPD BigInt не является частью спецификации, черновик предложения https://tc39.es/proposal-bigint/


Часовой пояс GMT +3, время: 23:08.