Neuroware
@Neuroware
Программист в свободное от работы время

Полезен ли алгоритм определения НЕпростоты числа с 3 операций?

Изучал одну теорию, оказалось она верна в 75-80% случаев. То есть для любого числа с вероятностью в 75% алгоритм говорит, что оно не простое, при этом совершается всего 3 математических операции, никаких циклов и т.п. Алгоритм остается с такой же вероятностью верным при любом числе знаков, проверял в очень широком диапозоне. Может ускорить другие алгоритмы проверки на простоту, то есть прежде чем проверять сложными алгоритмами можно прогнать этим и с 75% сказать что число не простое и дальше не проверять. Вопрос, нужно такое чудо кому ни будь или с такой погрешностью с него толку нет?

Алгоритм простой, есть определенные числа, при деления на которые остаток от деления будет простым числов в 75% случаев, поэтому если остаток от деления оказался не простым числом можно считать, что оно составное.

class MayBePrime
    {
        static bool[] charr = null;
        static int prime = 30030;//30030 для быстрого старта, ниже точность. Произведение первых простых чисел
        static public bool isMayBePrime(System.Numerics.BigInteger x)
        {
            if (charr == null)
            {
                charr = new bool[prime];
                for (int i = 0; i < prime; i++)
                {
                    if (NOD(prime, i) == 1)
                    {
                        charr[i] = true;
                    }
                    else
                    {
                        charr[i] = false;
                    }
                }
            }
            BigInteger n = x % prime;
            int n2 = 0;
            n2 = (int)n;
            if (charr[n2])
            {
                return true;
            }
            else
            {
                return false;
            }
        }
		static public int NOD(int a, int b)
        {
            if (a == 0) return b;
            if (b == 0) return a;
            if (a == b) return a;
            if (a == 1 || b == 1) return 1;
            if ((a % 2 == 0) && (b % 2 == 0)) return 2 * NOD(a / 2, b / 2);
            if ((a % 2 == 0) && (b % 2 != 0)) return NOD(a / 2, b);
            if ((a % 2 != 0) && (b % 2 == 0)) return NOD(a, b / 2);
            return NOD(b, Math.Abs(a - b));
        }
    }
  • Вопрос задан
  • 623 просмотра
Решения вопроса 1
Mrrl
@Mrrl
Заводчик кардиганов
Надеюсь, что остаток берётся всё-таки от деления на 390, а не на 389 - иначе смысла в коде вообще не видно.
Итак, у нас есть 77 простых чисел, меньших 390. Четыре из них - 2,3,5,13 - не более, чем мусор, дающий лишние срабатывания алгоритма. В самом деле, если x%390==3, то x делится на 3, а значит, оно составное. Остальные 73 числа годятся.
Получается такая картина. Для простого x величина x%390 - некоторое число, взаимно простое с 390. Причём распределены эти остатки более-менее равномерно (на картинке они выглядят как красные столбики). Всего этих остатков 96.
Если x - простое число, то с вероятностью 73/96 остаток будет простым числом, и алгоритм честно скажет "скорее, простое". С вероятностью 23/96 - те самые 25% - остаток окажется составным, и алгоритм ошибётся.
Если x - составное, то вероятность того, что число объявят "скорее, простым" будет равна 77/390-1/ln(x) (первое слагаемое - вероятность того, что остаток оказался простым, а второе - доля простых чисел в окрестности x).
Можно легко избавиться от ошибки первого вида: для этого надо в массив charr положить не простые числа, а числа, взаимно простые с 390. Тогда если алгоритм скажет "составное", то так и есть.
Можно было вместо 390 взять N=30030=2*3*5*7*11*13. Если массив будет заполнен числами, взаимно простыми с N, то отсеиваться, как составные, будет 4 числа из 5, а ложных отсеиваний простых чисел не будет вообще.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
bobrovskyserg
@bobrovskyserg
Произвольно выбранное число, не превышающее 10^5, с вероятностью 90% составное. Дальше - больше.
Тем не менее, откройте ваш способ.

UPDATE
def main(*mods):
    lo = 10 ** 5
    hi = 10 ** 9
    sieve = [1] * hi
    sieve[0] = sieve[1] = 0
    for i in range(int(hi ** 0.5) + 1):
        if sieve[i]:
            for j in range(i * i, hi, i):
                sieve[j] = 0
    primes = sum(sieve[lo:])
    for mod in mods:
        counter = [0] * 4
        for i in range(lo, hi):
            counter[sieve[i] * 2 + sieve[i % mod]] += 1
        print('\n{:>17n}\nprime \ check    0        1\n'
              '      0       {:7.3f}  {:7.3f}\n'
              '      1       {:7.3f}  {:7.3f}\n'.format(
            mod, *[float(i) / primes for i in counter]))

main(30029, 30030, 30047)

30029
prime \ check    0        1
      0        16.650    2.019
      1         0.892    0.108


            30030
prime \ check    0        1
      0        17.104    1.564
      1         0.437    0.563


            30047
prime \ check    0        1
      0        16.650    2.018
      1         0.892    0.108


Итак, имеем:
По замечанию Mrrl, знаменатель 30030 действительно подходит гораздо больше своих ближайших простых соседей. И всё же, даёт ли алгоритм какой-то выигрыш?
С одной стороны, он отбрасывает почти половину (0.437) простых, ошибочно объявляя их составными.
Но если этот фильтр вставить в реальный криптоалгоритм, он лишь ухудшит криптостойкость - половина простых известного вида будет выброшена из рассмотрения.
С другой стороны, на диапазоне lo..hi содержание простых около 5%, а алгоритм выдаёт простые к составным в пропорции более 1:3, что заметно лучше.
С третьей стороны, то, что автор назвал "всего 3 математических операции", выливается в предварительное вычисление сита длинной в 30030 чисел. При потоковом поиске простых это как-будто немного, но можно ли с теми же затратами сделать лучше?
Да, можно:
from itertools import cycle

def MillerRabin(n):
    return True  # допустим, здесь реализован тест Рабина-Миллера

def getprime(startsfrom):
    primes = (2, 3, 5, 7, 11, 13)
    mod = 1
    for p in primes:
        mod *= p  # итого mod = 30030
    steps, x0 = [], mod - 1
    for x in range(mod, x0 + mod + 1):
        if all(x % p for p in primes):
            steps.append(x - x0)
            x0 = x
    print(len(steps) / mod)  # 0.1918 - мы отсеяли 4/5 чисел 
    # входного потока, не потеряв ни единого простого. 
    # по итогу имеем те же 25% простых во входном потоке 
    # на интервале 10^5..10^9

    n = startsfrom // mod * mod - 1
    for step in cycle(steps):
        n += step
        if n > startsfrom and MillerRabin(n):
            return n


print(getprime(10 ** 500))
# 10^500+961
Ответ написан
@SeptiM
Я попробую угадать алгоритм. Для числа 41041 это работает?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы