пример кода, близкого к эталонному: ```rust pub fn nth(n: u32) -> u32 { let mut num = 1; for _ in 0..=n { loop { num += 1; if miller_rabin(num as u64) { break; } } } num } fn miller_rabin(n: u64) -> bool { const HINT: &[u64] = &[2]; // we have a strict upper bound, so we can just use the witness // table of Pomerance, Selfridge & Wagstaff and Jeaschke to be as // efficient as possible, without having to fall back to // randomness. Additional limits from Feitsma and Galway complete // the entire range of `u64`. See also: // https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test#Testing_against_small_sets_of_bases const WITNESSES: &[(u64, &[u64])] = &[ (2_046, HINT), (1_373_652, &[2, 3]), (9_080_190, &[31, 73]), (25_326_000, &[2, 3, 5]), (4_759_123_140, &[2, 7, 61]), (1_112_004_669_632, &[2, 13, 23, 1662803]), (2_152_302_898_746, &[2, 3, 5, 7, 11]), (3_474_749_660_382, &[2, 3, 5, 7, 11, 13]), (341_550_071_728_320, &[2, 3, 5, 7, 11, 13, 17]), (3_825_123_056_546_413_050, &[2, 3, 5, 7, 11, 13, 17, 19, 23]), (std::u64::MAX, &[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]), ]; if n % 2 == 0 { return n == 2; } if n == 1 { return false; } let mut d = n - 1; let mut s = 0; while d % 2 == 0 { d /= 2; s += 1 } let witnesses = WITNESSES .iter() .find(|&&(hi, _)| hi >= n) .map(|&(_, wtnss)| wtnss) .unwrap(); 'next_witness: for &a in witnesses.iter() { let mut power = mod_exp(a, d, n); assert!(power < n); if power == 1 || power == n - 1 { continue 'next_witness; } for _r in 0..s { power = mod_sqr(power, n); assert!(power < n); if power == 1 { return false; } if power == n - 1 { continue 'next_witness; } } return false; } true } fn mod_mul_(a: u64, b: u64, m: u64) -> u64 { (u128::from(a) * u128::from(b) % u128::from(m)) as u64 } fn mod_mul(a: u64, b: u64, m: u64) -> u64 { match a.checked_mul(b) { Some(r) => { if r >= m { r % m } else { r } } None => mod_mul_(a, b, m), } } fn mod_sqr(a: u64, m: u64) -> u64 { if a < (1 << 32) { let r = a * a; if r >= m { r % m } else { r } } else { mod_mul_(a, a, m) } } fn mod_exp(mut x: u64, mut d: u64, n: u64) -> u64 { let mut ret: u64 = 1; while d != 0 { if d % 2 == 1 { ret = mod_mul(ret, x, n) } d /= 2; x = mod_sqr(x, n); } ret } ``` ## Что такое Miller-Rabin (с нуля, метафора "Судья и Лжецы") Представь простое число как **честного судью**, а составное — как **лжеца**. У тебя есть **несколько свидетелей** (маленькие числа 2, 3, 7...), которые задают судье **сложные вопросы**. **Правило игры:** Если судья честный (число простое), он всегда отвечает правильно. Если лжец (составное), то с высокой вероятностью **хотя бы один свидетель** поймает его на лжи. Miller-Rabin — это **математический допрос**: мы заставляем число доказать свою честность, вычисляя сложные степени по модулю. ## Шаг 1: nth(n) — Простой перебор с MR-тестом ```rust pub fn nth(n: u32) -> u32 { let mut num = 1; // начинаем с 2 (1-е простое) for _ in 0..=n { // ищем n+1 простое число loop { num += 1; if miller_rabin(num as u64) { break; } // MR говорит "простое!" } } num } ``` **Логика:** "Ходи по числам, пока не найдешь n простых". Медленно (O(n log n)), но **корректно**. Для Exercism (n до 10k) работает мгновенно. ## Шаг 2: Подготовка MR-теста (d и s) ```rust let mut d = n - 1; let mut s = 0; while d % 2 == 0 { d /= 2; s += 1 } ``` **Математика:** Каждое простое удовлетворяет **малой теореме Ферма**: \(a^{n-1} \equiv 1 \pmod n\). Но вместо прямого вычисления \(a^{n-1}\) мы раскладываем показатель: \[ n-1 = d \cdot 2^s \] где \(d\) — нечетное. **Почему?** Чтобы проверять промежуточные квадраты. **Метафора:** "Не спрашивай сразу 'ты честный?', а сначала проверь базовое свойство, потом возводи в квадрат шаг за шагом". ## Шаг 3: Witness Table (функциональный стиль) ```rust let witnesses = WITNESSES.iter() .find(|&&(hi, _)| hi >= n) // ищем нужный диапазон .map(|&(_, wtnss)| wtnss) // извлекаем список свидетелей .unwrap(); ``` **Гениальность:** Для каждого диапазона известен **минимальный набор свидетелей**, гарантирующий 100% точность. | Диапазон | Свидетели | Тестов | | ---------- | --------------------- | ------ | | < 2047 | | 1 | | < 4.7e9 | [1] | 3 | | < u64::MAX | [7][1][2][3][4][5][6] | 12 | **Функциональный стиль:** `iter().find().map()` — чистый, без мутабельности, легко тестировать. ## Шаг 4: Основной цикл MR (сердце алгоритма) ```rust for &a in witnesses.iter() { // для каждого свидетеля let mut power = mod_exp(a, d, n); // x0 = a^d mod n if power == 1 || power == n-1 { continue } // тест пройден for _r in 0..s { // s раз возводим в квадрат power = mod_sqr(power, n); // xr+1 = xr^2 mod n if power == n-1 { continue 'next_witness } // тест пройден! if power == 1 { return false } // составное! } return false; // все свидетели поймали на лжи } ``` **Логика теста:** 1. Вычисляем \(x_0 = a^d \mod n\) 2. Если \(x_0 = 1\) или \(x_0 = n-1\) → свидетель доволен 3. Иначе делаем \(s\) квадратов: \(x_{r+1} = x_r^2 \mod n\) 4. Если хоть раз получили \(n-1\) → свидетель доволен 5. Если дошли до конца → **лжец пойман!** ## Шаг 5: Быстрое возведение в степень (Binary Exponentiation) ```rust fn mod_exp(mut x: u64, mut d: u64, n: u64) -> u64 { let mut ret: u64 = 1; while d != 0 { if d % 2 == 1 { // если бит степени = 1 ret = mod_mul(ret, x, n); // ret *= x } d /= 2; // сдвиг степени x = mod_sqr(x, n); // x = x^2 (для следующего бита) } ret } ``` **Метафора:** "Не умножай 2^1000 = 2×2×2... 1000 раз. Используй (2^2)^2 = 2^4, ((2^4)^2)^2 = 2^16 и т.д." O(log d) умножений вместо O(d). ## Шаг 6: Безопасное умножение по модулю (u64 → u128) ```rust fn mod_mul(a: u64, b: u64, m: u64) -> u64 { match a.checked_mul(b) { Some(r) => if r >= m { r % m } else { r }, None => mod_mul_(a, b, m), // fallback на u128 } } ``` **Проблема:** \(2^{64}-1 \times 2^{64}-1 = 2^{128}\), переполнение! **Решение:** `checked_mul` + fallback на `u128::from(a) * u128::from(b) % u128::from(m)`. **Оптимизация `mod_sqr`:** Для \(a < 2^{32}\) обычное `a*a` быстрее `u128`. ## Итог: Почему код Production-Ready ``` ✅ Детерминированный (не вероятностный) ✅ O(√n) на свидетель (с log n на mod_exp) ✅ Безопасен от overflow (u128 fallback) ✅ Функциональный стиль (иммутабельные итераторы) ✅ Таблица свидетелей покрывает весь u64 ✅ Микрооптимизации (mod_sqr для малых чисел) ``` **Когда использовать:** Проверка **конкретных** больших чисел (10^18 + 7?). **Когда НЕ использовать:** Генерация последовательности простых — тут sieve в 1000x быстрее. Этот код — **золотой стандарт** для криптографии и системного программирования.[4] [1](https://github.com/SergioBenitez/Rocket/issues/1522) [2](https://gliderkite.github.io/posts/sr-cache/) [3](https://stackoverflow.com/questions/16761898/redis-as-write-back-view-count-cache-for-mysql) [4](https://github.com/jedisct1/rust-sieve-cache) [5](https://dev.to/artslob/32-rust-crates-you-better-know-about-4aa7) [6](https://lib.rs/crates/staticvec) [7](https://www.perplexity.ai/search/444a1554-6943-4987-9a27-5923865aea84) ## Малая теорема Ферма (шаг 2 MR) **Формулировка:** Если \( p \) — простое число и \( a \) не делится на \( p \), то: \[ a^{p-1} \equiv 1 \pmod{p} \] или эквивалентно: \[ a^p \equiv a \pmod{p} \] [1] ### Метафора "Кольцо часов" Представь \( p = 5 \) простое число как **циферблат с 5 делениями** (0,1,2,3,4). Умножение на \( a = 3 \) — это **поворот стрелки**: ``` Стрелка на 1 → ×3 = 3 Стрелка на 2 → ×3 = 6 ≡ 1 (mod 5) Стрелка на 3 → ×3 = 9 ≡ 4 (mod 5) Стрелка на 4 → ×3 = 12 ≡ 2 (mod 5) ``` **Важно:** Поворот на \( 1,2,3,4 \) дал **все возможные позиции** (1,3,4,2) — **полный цикл**! Умножение на 3 **проходится по всему кольцу**. **Теорема говорит:** Если \( p \) простое, то любое \( a \) (не кратное \( p \)) за \( p-1 \) повортов **вернется в исходную точку**. ### Пример вычисления ```rust // p = 13 (простое), a = 5 5^12 mod 13 = ? // малая теорема: должно быть 1 5^1 ≡ 5 5^2 ≡ 25 ≡ 12 ≡ -1 5^4 ≡ (-1)^2 ≡ 1 5^8 ≡ 1^2 ≡ 1 5^12 = 5^8 × 5^4 ≡ 1 × 1 ≡ 1 ✓ ``` ### Почему это работает (доказательство через перестановки) **Ключевой инсайт:** Остатки \( 1, 2, ..., p-1 \) при умножении на \( a \) дают **перестановку** тех же чисел: ```rust p = 7, a = 3 1×3 = 3 2×3 = 6 3×3 = 9 ≡ 2 4×3 = 12 ≡ 5 5×3 = 15 ≡ 1 6×3 = 18 ≡ 4 Получили: [3,6,2,5,1,4] — все числа 1..6 в другом порядке! ``` **Произведение всех элементов одинаково:** \[ 1 \times 2 \times \dots \times (p-1) = a \times 2a \times \dots \times (p-1)a \pmod{p} \] \[ (p-1)! \equiv a^{p-1} \times (p-1)! \pmod{p} \] Сокращаем \( (p-1)! \) (оно не 0 mod p): \[ 1 \equiv a^{p-1} \pmod{p} \] ### Почему MR использует именно эту теорему **Прямая проверка \( a^{p-1} \mod p \) неэффективна** — показатель степени огромный (до \( 2^{64} \)). **Хитрость MR:** Вместо \( a^{p-1} \) проверяем **промежуточные квадраты**: \[ n-1 = d \times 2^s \] \[ a^{n-1} = (a^d)^{2^s} \] Если \( a^d \equiv 1 \) или хотя бы один квадрат дает \( n-1 \), то по свойствам степеней теорема выполняется. **Метафора:** Вместо "сделай 1 трлн повортов стрелки" говорим "сделай 40 повортов, но проверяй промежуточные позиции". ### Проверка на практике ```rust fn fermat_test(n: u64, a: u64) -> bool { mod_exp(a, n-1, n) == 1 // прямая малая теорема } // Но! 561 — псевдопростое по основанию 2: // 2^560 ≡ 1 mod 561, хотя 561 = 3×11×17 ``` **Вывод:** Малая теорема — **необходимое** условие простоты, но **недостаточное** (есть "лжецы"). MR усиливает тест **структурированными промежуточными проверками** + **несколькими основаниями**. Теперь понятен шаг 2? Идем к witness table? 🚀 [1](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D0%BB%D0%B0%D1%8F_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%A4%D0%B5%D1%80%D0%BC%D0%B0) [2](https://math.mosolymp.ru/upload/files/2022/other/1568/8-1/Malaya_teorema_Ferma.pdf) [3](https://foxford.ru/wiki/matematika/malaya-teorema-ferma) [4](https://www.lirmm.fr/~ashen/mathclass/09fermat-prob.pdf) [5](https://wiki.algocode.ru/index.php?title=%D0%9C%D0%B0%D0%BB%D0%B0%D1%8F_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%A4%D0%B5%D1%80%D0%BC%D0%B0) [6](https://kvant.mccme.ru/pdf/2000/04/kv0400ferm.pdf) [7](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%A4%D0%B5%D1%80%D0%BC%D0%B0_%E2%80%94_%D0%AD%D0%B9%D0%BB%D0%B5%D1%80%D0%B0) [8](https://journalpro.ru/articles/malaya-teorema-ferma-i-ee-primenenie-v-kriptosistemakh/) [9](https://www.reddit.com/r/math/comments/2v6db7/intuition_for_fermats_little_theorem/) [10](https://math.fandom.com/ru/wiki/%D0%9C%D0%B0%D0%BB%D0%B0%D1%8F_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%A4%D0%B5%D1%80%D0%BC%D0%B0)