Я думал об алгоритме в делении больших чисел: делении с остатком bigint C на bigint D, где мы знаем представление C в базе b, а D имеет вид b ^ k-1. Вероятно, это проще всего показать на примере. Попробуем делить C = 21979182173 на D = 999.
- Мы записываем число как наборы из трех цифр: 21 979 182 173
- Мы берем суммы (по модулю 999) последовательных множеств, начиная слева: 21 001 183 356
- Добавим 1 к тем наборам, которые предшествуют тем, где мы "перешли 999": 22 001 183 356
Действительно, 21979182173/999 = 22001183 и остаток 356.
Я вычислил сложность и, если я не ошибаюсь, алгоритм должен работать в O (n), n - количество цифр C в представлении base b. Я также сделал очень грубую и неоптимизированную версию алгоритма (только для b = 10) в С++, протестировал его против универсального алгоритма общего GMP, и он действительно выглядит лучше, чем GMP. Я не мог найти ничего подобного в любом месте, где бы я ни выглядел, поэтому мне пришлось прибегнуть к тестированию его против общего деления.
Я нашел несколько статей, в которых обсуждаются, по-видимому, очень похожие вопросы, но ни один из них не концентрируется на реальных реализациях, особенно на основаниях, отличных от 2. Я полагаю, что из-за того, как номера хранятся внутри, хотя указанный алгоритм кажется полезно, например, для b = 10, даже учитывая это. Я также пытался связаться с некоторыми другими людьми, но, опять же, безрезультатно.
Таким образом, мой вопрос будет: есть ли статья или книга или что-то там, где описан вышеописанный алгоритм, возможно, обсуждая реализации? Если нет, было бы ли для меня разумным попытаться реализовать и протестировать такой алгоритм, скажем, в C/С++, или этот алгоритм каким-то образом изначально плох?
Кроме того, я не программист, и, хотя я достаточно хорошо разбираюсь в программировании, я, по общему признанию, не очень хорошо знаю компьютерные "внутренние". Таким образом, простите мое невежество - очень возможно, что в этом посте есть одна или несколько очень глупых вещей. Простите еще раз.
Спасибо большое!
Дальнейшее разъяснение вопросов, поднятых в комментариях/ответах:
Спасибо, каждый - поскольку я не хотел комментировать все отличные ответы и советы с одной и той же вещью, я просто хотел бы затронуть один момент, о котором вы много говорили.
Я прекрасно понимаю, что работа на основаниях 2 ^ n, вообще говоря, является, несомненно, наиболее эффективным способом ведения дел. Практически все библиотеки bigint используют 2 ^ 32 или что-то еще. Однако, что, если (и, я подчеркиваю, это было бы полезно только для этого конкретного алгоритма!), Мы реализуем bigints как массив цифр в базе b? Разумеется, здесь мы должны быть "разумными": b = 10, наиболее естественный случай, представляется достаточно разумным. Я знаю, что он более или менее неэффективен, учитывая память и время, учитывая, как числа хранятся внутри, но я был в состоянии, если мои (основные и, возможно, как-то ошибочные) тесты верны, дают результаты быстрее, чем общее разделение GMP, что имело бы смысл в реализации такого алгоритма.
Замечания о Ninefingers Мне пришлось бы использовать в этом случае дорогостоящую операцию modulo. Надеюсь, что нет: я вижу, если старый + новый скрещенный, скажем, 999, просто посмотрев на количество цифр старого + нового + 1. Если у нас 4 цифры, мы закончили. Более того, начиная с старых < 999 и новых <= 999, мы знаем, что если старый + новый + 1 имеет 4 цифры (он не может иметь больше), то (старый + новый)% 999 равен удалению самой левой цифры ( старый + новый + 1), который, я полагаю, мы можем сделать дешево.
Конечно, я не оспариваю очевидных ограничений этого алгоритма и не утверждаю, что он не может быть улучшен - он может делиться только с определенным классом чисел, и мы должны априори знать представление дивиденда в базе b, Однако, например, для b = 10 последнее кажется естественным.
Теперь, скажем, мы реализовали бонусы, как я изложил выше. Скажем C = (a_1a_2... a_n) в базе b и D = b ^ k-1. Алгоритм (который может быть, вероятно, гораздо более оптимизирован) будет идти следующим образом. Я надеюсь, что не так много опечаток.
- если k > n, мы, очевидно, выполняем
- добавьте нуль (т.е. a_0 = 0) в начале C (на всякий случай мы попытаемся разделить, скажем, 9999 с 99)
- l = n% k (mod для "правильных" целых чисел - не должно быть слишком дорого)
- old = (a_0... a_l) (первый набор цифр, возможно с меньшим числом символов)
- для (i = l + 1; я < n; я = я + k) (Мы будем иметь пол (n/k) или так итерации)
- Новый = (a_i... а_ (я + к-1))
- new = new + old (это добавление bigint, таким образом, O (k))
- aux = new + 1 (опять же, добавление bigint - O (k) - что мне не нравится)
- если aux имеет более чем k цифр
- удалить первую цифру aux
- old = old + 1 (добавление bigint еще раз)
- заполнить старый нулями в начале, чтобы он имел столько же цифр, сколько и должно
- (a_ (i-k)... a_ (i-1)) = old (если я = l + 1, (a _ 0... a _ l) = old)
- Новый = Окс
- заполнить ноль нулями в начале, чтобы он имел столько же цифр, сколько и должно
- (a_i... а_ (я + к-1) = новый
- Quot = (A_0... а_ (п-к + 1))
- бэр = новый
Там, спасибо за обсуждение этого со мной - как я уже сказал, мне кажется, что это интересный алгоритм "частного случая", который пытается реализовать, протестировать и обсудить, если никто не видит в нем каких-либо фатальных изъянов. Если это не так широко обсуждается до сих пор, еще лучше. Пожалуйста, дай мне знать, что ты думаешь. Извините за длинный пост.
Кроме того, еще несколько личных комментариев:
@Ninefingers: у меня есть некоторые (очень простые!) знания о том, как работает GMP, что он делает и общих алгоритмов разделения bigint, поэтому я смог понять большую часть ваших аргументов. Я также знаю, что GMP очень оптимизирован и в какой-то мере настраивает себя для разных платформ, поэтому я, конечно же, не пытаюсь "победить" в целом - это кажется столь же плодотворным, как атака танком с заостренной палкой. Однако это не идея этого алгоритма - он работает в очень особых случаях (что GMP, похоже, не охватывает). На несвязанной ноте, уверены ли вы, что общие деления выполняются в O (n)? Самое большее, что я видел, это M (n). (И это может, если я правильно понимаю, на практике (Schönhage-Strassen и т.д.) Не достигает O (n). Алгоритм Фюрера, который до сих пор не достигает O (n), является, если я прав, почти чисто теоретическое.)
@Avi Berger: На самом деле это не похоже на то, что "выкидывать девятки", хотя идея схожа. Однако вышеупомянутый алгоритм должен работать все время, если я не ошибаюсь.