Начин да се намери най-близкото просто число до дълго цяло число без знак (широко 32 бита) в C?

Търся начин да намеря най-близкото просто число. По-голяма или по-малка от, няма значение, просто най-близката ( без препълване, за предпочитане. ) Що се отнася до скоростта, ако може да я изчисли за приблизително 50 милисекунди на 1GHz машина (в софтуер, работеща в Linux), бих бъди екстатичен.


person Erkling    schedule 08.03.2012    source източник
comment
Какво ще кажете да имате масив от всички прости числа с цял диапазон?   -  person MByD    schedule 08.03.2012
comment
Е, в зависимост от броя на простите числа от 0x0 до 0xFFFFFFFF, предполагам, че това би бил най-подходящият начин да го направим.   -  person Erkling    schedule 08.03.2012
comment
Ето алгоритъм за намиране на прости числа, той се изгражда от 2 нагоре, en.wikipedia.org/wiki/Sieve_of_Eratosthenes.   -  person twain249    schedule 08.03.2012
comment
@Binyamin: изпълнимо, но това е доста голяма таблица (около 200 милиона записа, мисля).   -  person Michael Burr    schedule 08.03.2012
comment
Има 203280221 прости числа под 2^32. Такава таблица би заела около 800MB. Това много ли е?   -  person R. Martinho Fernandes    schedule 08.03.2012
comment
@Erkling Добре, така че започвате от конкретното си число, правите тест за простота на това число, увеличавайки го, докато намерите едно, и правите същото, като го намалявате, и избирате едно от евентуалните 2, което е най-близко   -  person nos    schedule 08.03.2012
comment
Считайте го за твърде много, предвидено е да бъде сравнително малък, тъй като машината, на която ще работи, има 256MB RAM. @nos Виждам, предполагам, че това би било относително подходящо.   -  person Erkling    schedule 08.03.2012


Отговори (2)


АКТУАЛИЗАЦИЯ 2: Коригирани (по тежък начин) някои грешки, които причиняваха грешни отговори за малки n. Благодаря на Брет Хейл, че забеляза! Също така са добавени някои твърдения за документиране на някои предположения.

АКТУАЛИЗАЦИЯ: Кодирах това и изглежда достатъчно бързо за вашите изисквания (решени 1000 произволни екземпляра от [2^29, 2^32-1] за ‹100ms, на 2,2GHz машина -- не е строг тест, но въпреки това е убедителен).

Написано е на C++, тъй като това беше моят код на sieve (от който адаптирах), но преобразуването в C трябва да е лесно. Използването на памет също е (сравнително) малко, което можете да видите чрез проверка.

Можете да видите, че поради начина, по който се извиква функцията, върнатото число е най-близкото просто число, което се побира в 32 бита, но всъщност това е едно и също нещо, тъй като простите числа около 2^32 са 4294967291 и 4294967311.

Опитах се да се уверя, че няма да има грешки поради целочислено препълване (тъй като имаме работа с числа точно до UINT_MAX); дано не съм сгрешил там. Кодът може да бъде опростен, ако искате да използвате 64-битови типове (или сте знаели, че вашите числа ще бъдат по-малки от 2^32-256), тъй като няма да се налага да се притеснявате от обвиване в условията на цикъла. Също така тази идея се мащабира за по-големи числа, стига да сте готови да изчислите/съхраните малките прости числа до необходимия лимит.

Трябва да отбележа също, че ситото за малки прости числа работи доста бързо за тези числа (4-5 ms от грубо измерване), така че ако имате особено недостиг на памет, стартирането му всеки път, вместо да съхранявате малките прости числа, е възможно (вие вероятно бих искал да направя масивите mark[] по-ефективни в пространството в този случай)

#include <iostream>
#include <cmath>
#include <climits>
#include <cassert>

using namespace std;

typedef unsigned int UI;

const UI MAX_SM_PRIME = 1 << 16;
const UI MAX_N_SM_PRIMES = 7000;
const UI WINDOW = 256;

void getSMPrimes(UI primes[]) {
  UI pos = 0;
  primes[pos++] = 2;

  bool mark[MAX_SM_PRIME / 2] = {false};
  UI V_SM_LIM = UI(sqrt(MAX_SM_PRIME / 2));
  for (UI i = 0, p = 3; i < MAX_SM_PRIME / 2; ++i, p += 2)
    if (!mark[i]) {
      primes[pos++] = p;
      if (i < V_SM_LIM)
        for (UI j = p*i + p + i; j < MAX_SM_PRIME/2; j += p)
          mark[j] = true;
      }
  }

UI primeNear(UI n, UI min, UI max, const UI primes[]) {
  bool mark[2*WINDOW + 1] = {false};

  if (min == 0) mark[0] = true;
  if (min <= 1) mark[1-min] = true;

  assert(min <= n);
  assert(n <= max);
  assert(max-min <= 2*WINDOW);

  UI maxP = UI(sqrt(max));
  for (int i = 0; primes[i] <= maxP; ++i) {
    UI p = primes[i], k = min / p;
    if (k < p) k = p;
    UI mult = p*k;
    if (min <= mult)
      mark[mult-min] = true;
    while (mult <= max-p) {
      mult += p;
      mark[mult-min] = true;
      }
    }

  for (UI s = 0; (s <= n-min) || (s <= max-n); ++s)
    if ((s <= n-min) && !mark[n-s-min])
      return n-s;
    else if ((s <= max-n) && !mark[n+s-min])
      return n+s;

  return 0;
  }

int main() {
  UI primes[MAX_N_SM_PRIMES];
  getSMPrimes(primes);

  UI n;
  while (cin >> n) {
    UI win_min = (n >= WINDOW) ? (n-WINDOW) : 0;
    UI win_max = (n <= UINT_MAX-WINDOW) ? (n+WINDOW) : UINT_MAX;

    if (!win_min)
      win_max = 2*WINDOW;
    else if (win_max == UINT_MAX)
      win_min = win_max-2*WINDOW;

    UI p = primeNear(n, win_min, win_max, primes);
    cout << "found nearby prime " << p << " from window " << win_min << ' ' << win_max << '\n';
    }
  }

Можете да пресеете интервали в този диапазон, ако знаете прости числа до 2^16 (има само 6542 ‹= 2^16; трябва да отидете малко по-високо, ако самото просто число може да бъде по-голямо от 2^32 - 1). Не непременно най-бързият начин, но много прост, и по-фантастичните първични техники за тестване наистина са подходящи за много по-големи диапазони.

По принцип направете редовно Сито на Ератостен, за да получите "малките" прости числа (да речем първите 7000). Очевидно трябва да направите това само веднъж в началото на програмата, но трябва да е много бързо.

След това, ако приемем, че вашето „целево“ число е „a“, разгледайте интервала [a-n/2, a+n/2) за някаква стойност на n. Вероятно n = 128 е разумно място за начало; може да се наложи да опитате съседни интервали, ако всичките числа в първия са съставни.

За всяко „малко“ просто p задраскайте неговите кратни в диапазона, като използвате деление, за да намерите откъде да започнете. Една оптимизация е, че трябва само да започнете да зачертавате кратни, започващи от p*p (което означава, че можете да спрете да разглеждате прости числа, след като p*p е над интервала).

Повечето прости числа, с изключение на първите няколко, ще имат едно или нула кратни вътре в интервала; за да се възползвате от това, можете предварително да игнорирате кратните на първите няколко прости числа. Най-простото нещо е да игнорирате всички четни числа, но не е необичайно да игнорирате кратните на 2, 3 и 5; това оставя цели числа, съответстващи на 1, 7, 11, 13, 17, 19, 23 и 29 по мод 30 (има осем, които се съпоставят добре с битовете на байт при пресяване на голям диапазон).

... Някак си тръгна по допирателна там; както и да е, след като сте обработили всички малки прости числа (до p*p > a+n/2), просто поглеждате в интервала за числа, които не сте задраскали; тъй като искате най-близо до началото да гледате там и да търсите навън и в двете посоки.

person Sumudu Fernando    schedule 08.03.2012
comment
Ако Брет Хейл е прав за най-голямата разлика, тогава вашето n трябва да е 335 или може би няколко по-голямо. - person Mark Ransom; 08.03.2012
comment
Също така бих изчислил предварително простите числа под 2^16 в статична таблица и бих използвал двоично търсене, когато a е достатъчно малък. - person Mark Ransom; 08.03.2012
comment
Двоичното търсене е добра идея (не казах статична таблица, но това наистина имах предвид). Не знам колко често се среща най-голямата празнина, така че може да е по-добре в средния случай да се използва n, по-малко от 335, след което да се тестват съседни интервали, ако е необходимо (въпреки че е много вероятно разликата между n = 128 и n = 512 да е незначителна ; Използвах този тип конструкция за изграждане на общо сегментирано сито и открих, че интервали с размер ~20000 са доста ефективни) - person Sumudu Fernando; 09.03.2012
comment
Подозирам, че ситото ще бъде най-бавната част от този процес, би било жалко да трябва да го правите два пъти на два различни интервала. По-добре да сте сигурни от първия път. - person Mark Ransom; 09.03.2012
comment
Входът „3“ ми казва, че най-близкото просто число е „1“, а „13“ дава „23“. - person Brett Hale; 09.03.2012
comment
Да...направих някои предположения, които са верни само за големи числа. Ще се актуализира с корекция след секунда. Благодаря за проверката! - person Sumudu Fernando; 09.03.2012
comment
Ако входът е по-голям от 2^32-256, можете да се справите с няколко if израза. - person Mark Ransom; 09.03.2012
comment
... справям се с няколко инструкции if (всичко, което имах предвид в описанието беше, че тези тестове могат да бъдат премахнати, ако не трябва да се тревожим за тези случаи); този код трябва да работи за всеки вход, който се вписва в 32-bit unsigned int, ако приемем, че не съм направил други грешки като това, което Брет Хейл хвана. - person Sumudu Fernando; 10.03.2012

Най-големият прост пропуск в диапазона до (2^32 - 1) е (335 ). Има (6542) прости числа, по-малки от (2^16), които могат да бъдат таблични и използвани за пресяване на последователни нечетни стойности след еднократна настройка. Очевидно е, че само простите числа ‹= floor(sqrt(candidate)) трябва да бъдат тествани за конкретна кандидатска стойност.

Алтернативно: детерминистичният вариант на Miller-Rabin тест, със SPRP бази: {2, 7, 61} е достатъчно, за да докаже първичност за 32-битова стойност. Поради сложността на теста (изисква степенуване и т.н.), съмнявам се, че ще бъде толкова бърз за толкова малки кандидати.

Редактиране: Всъщност, ако умножаването/намаляването може да се запази до 32-битово степенуване (може да се нуждае от 64-битова поддръжка), M-R тестът може да е по-добър. Основните празнини обикновено са много по-малки, което прави разходите за настройка на ситото прекомерни. Без големи справочни таблици и т.н. може също да получите тласък от по-добро местоположение на кеша.

Освен това: Произведението на простите числа {2, 3, 5, 7, 11, 13, 17, 19, 23} = (223092870). Изрично тествайте всеки кандидат в [2, 23]. Изчислете най-голям общ делител: g = gcd(u, 223092870UL). Ако (g != 1), кандидатът е съставен. Ако (g == 1 && u < (29 * 29)), кандидатът (u > 23) определено е първичен. В противен случай преминете към по-скъпите тестове. Единичен gcd тест, използващ 32-битова аритметика, е много евтин и според теоремата на Мертенс (?) това ще открие ~ 68,4% от всички нечетни съставни числа.

person Brett Hale    schedule 08.03.2012
comment
Този стил може да бъде продължен със следващите 5 прости числа {29, 31, 37, 41, 43}, чийто продукт е 58642669. и т.н. и т.н. Но броят на простите числа във всеки набор се свива, тъй като в крайна сметка продуктът ще надхвърли 32-бита. - person Jesse Chisholm; 26.12.2020