Sito Eratostenesa w C++ przesiew liczb do 10 16 i większych



Podobne dokumenty
znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Wstęp do Informatyki zadania ze złożoności obliczeniowej z rozwiązaniami

1. Napisz program, który wyświetli Twoje dane jako napis Witaj, Imię Nazwisko. 2. Napisz program, który wyświetli wizytówkę postaci:

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

for (inicjacja_warunkow_poczatkowych; wyrazenie_warunkowe; wyrazenie_zwiekszajace) { blok instrukcji; }

1 Podstawy c++ w pigułce.

- - Ocena wykonaniu zad3. Brak zad3

Nazwa implementacji: Nauka języka Python pętla for. Autor: Piotr Fiorek

for (inicjacja_warunkow_poczatkowych(końcowych); wyrazenie_warunkowe; wyrazenie_zwiekszajace(zmniejszające)) { blok instrukcji; }

do instrukcja while (wyrażenie);

Iteracje. Algorytm z iteracją to taki, w którym trzeba wielokrotnie powtarzać instrukcję, aby warunek został spełniony.

1. Liczby naturalne, podzielność, silnie, reszty z dzielenia

Instrukcja wyboru, pętle. 2 wykład. Podstawy programowania - Paskal

Pętle. Dodał Administrator niedziela, 14 marzec :27

INFORMATYKA Z MERMIDONEM. Programowanie. Moduł 5 / Notatki

Rekurencja (rekursja)

Przykładowe zadania z teorii liczb

Podstawy informatyki. Informatyka stosowana - studia niestacjonarne. Grzegorz Smyk

Algorytmy i struktury danych. Wykład 4

2. Liczby pierwsze i złożone, jednoznaczność rozkładu na czynniki pierwsze, największy wspólny dzielnik, najmniejsza wspólna wielokrotność.

6. Pętle while. Przykłady

Wstęp do Programowania, laboratorium 02

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Nazwa implementacji: Nauka języka Python wyrażenia warunkowe. Autor: Piotr Fiorek. Opis implementacji: Poznanie wyrażeń warunkowych if elif - else.

WHILE (wyrażenie) instrukcja;

Język C, tablice i funkcje (laboratorium, EE1-DI)

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

WHILE (wyrażenie) instrukcja;

Tablice cz. I Tablice jednowymiarowe, proste operacje na tablicach

WYKŁAD 8. Funkcje i algorytmy rekurencyjne Proste przykłady. Programy: c3_1.c..., c3_6.c. Tomasz Zieliński

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

TABLICE W JĘZYKU C/C++ typ_elementu nazwa_tablicy [wymiar_1][wymiar_2]... [wymiar_n] ;

Programowanie w Baltie klasa VII

1 Podstawy c++ w pigułce.

Algorytm Euklidesa. Największy wspólny dzielnik dla danych dwóch liczb całkowitych to największa liczba naturalna dzieląca każdą z nich bez reszty.

tablica: dane_liczbowe

Zadania do samodzielnego rozwiązania

ALGORYTMY Algorytm poprawny jednoznaczny szczegółowy uniwersalny skończoność efektywność (sprawność) zmiennych liniowy warunkowy iteracyjny

Proste programy w C++ zadania

Schematy blokowe I. 1. Dostępne bloki: 2. Prosty program drukujący tekst.

Podstawy Programowania Podstawowa składnia języka C++

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Chen Prime Liczby pierwsze Chena

1,3,4,2,3,4,0,1,4,5,0. Wówczas największa suma trzech kolejnych liczb (zaznaczone na czerwono) wynosi:

Pytania sprawdzające wiedzę z programowania C++

Algorytm. a programowanie -

Blockly Kodowanie pomoc.

Instrukcje sterujące

Pzetestuj działanie pętli while i do...while na poniższym przykładzie:

Warunki logiczne instrukcja if

ALGORYTMY I STRUKTURY DANYCH

Lekcja : Tablice + pętle

Podstawy Programowania C++

4. Funkcje. Przykłady

5. Rekurencja. Przykłady

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

Programowanie w C/C++ Instrukcje - konstrukcje powtórka. LABORKA Piotr Ciskowski

Program 14. #include <iostream> #include <ctime> using namespace std;

Język C, instrukcje sterujące (laboratorium)

Zestaw A-1: Organizacja plików: Oddajemy tylko źródła programów (pliki o rozszerzeniach.adb i.ads)!!! Zad. 1: 4,3,3 2,2,1 Zad. 2: 3,3,3 Zad.

Wstęp do programowania

1 Powtórzenie wiadomości

Język C, tablice i funkcje (laboratorium)

Podstawy programowania. Wykład: 4. Instrukcje sterujące, operatory. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Wiadomości wstępne Środowisko programistyczne Najważniejsze różnice C/C++ vs Java

Podstawy informatyki. Informatyka stosowana - studia niestacjonarne. Grzegorz Smyk. Wydział Inżynierii Metali i Informatyki Przemysłowej

Zestaw zadań dotyczących liczb całkowitych

W języku C dostępne są trzy instrukcje, umożliwiające tworzenie pętli: for, while oraz do. for (w1;w2;w3) instrukcja

Czy liczby pierwsze zdradzą swoje tajemnice? Czy liczby pierwsze zdradzą swoje tajemnice?

2 Przygotował: mgr inż. Maciej Lasota

Programowanie - instrukcje sterujące

Programowanie komputerowe. Zajęcia 1

Podstawowe algorytmy i ich implementacje w C. Wykład 9

do instrukcja while(wyrażenie);

Programowanie dynamiczne

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

lekcja 8a Gry komputerowe MasterMind

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

Języki formalne i techniki translacji

Programowanie strukturalne i obiektowe. Funkcje

Jak zawsze wyjdziemy od terminologii. While oznacza dopóki, podczas gdy. Pętla while jest

Część XVII C++ Funkcje. Funkcja bezargumentowa Najprostszym przypadkiem funkcji jest jej wersja bezargumentowa. Spójrzmy na przykład.

Jarosław Wróblewski Matematyka Elementarna, zima 2015/16

Sortowanie zewnętrzne

Pętle i tablice. Spotkanie 3. Pętle: for, while, do while. Tablice. Przykłady

Pliki. Informacje ogólne. Obsługa plików w języku C


Podstawy algorytmiki i programowania - wykład 2 Tablice dwuwymiarowe cd Funkcje rekurencyjne

Zaprojektować i zaimplementować algorytm realizujący następujące zadanie.

Wyszukiwanie. Wyszukiwanie binarne

Konstrukcje warunkowe Pętle

2.8. Algorytmy, schematy, programy

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

1. Liczby wymierne. x dla x 0 (wartością bezwzględną liczby nieujemnej jest ta sama liczba)

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

ALGORYTMY MATEMATYCZNE Ćwiczenie 1 Na podstawie schematu blokowego pewnego algorytmu (rys 1), napisz listę kroków tego algorytmu:

Zestaw C-11: Organizacja plików: Oddajemy tylko źródła programów (pliki o rozszerzeniach.cpp i.h)!!! Zad. 1: Zad. 2:

Transkrypt:

Sito Eratostenesa w C++ przesiew liczb do 10 16 i większych Ogólnie o sicie Eratostenesa Algorytm, opracowany przez Eratostenesa, służy do wyszukiwania liczb pierwszych (liczb mających dokładnie dwa dzielniki). Najbardziej naturalna metoda określania, czy liczba jest pierwsza, polega na sprawdzeniu jej podzielności przez wszystkie liczby naturalne mniejsze od niej. Eratostenes zauważył, że można ograniczyć ilość dzielników. Doszedł on do wniosku, że mając daną liczbę naturalną N, w celu wyznaczenia wszystkich liczby pierwszych mniejszych od N, należy znaleźć największą liczbę pierwszą która spełni warunek p 2 <=N (gdzie p liczba pierwsza), a wszystkie większe od niej sprawdzić pod względem podzielności przez liczby pierwsze mniejsze lub równe p. Przykładowo dla N=100: p=7 (bo 7 2 =49, a 11 2 =121) Liczby pierwsze mniejsze od p: p 1 =2 p 2 =3 p 3 =5 Sprawdzamy liczby od 8 do 100. Jeśli dzielenie daje resztę inną niż 0, dla każdej z powyższych liczb pierwszych, użytych w postaci dzielników, liczba jest pierwsza. Dodatkowo ze zbioru otrzymanych liczb należy odrzucić 1, która ma tylko jeden dzielnik. Łatwo zauważyć, że największa z liczb pierwszych, uzyskanych w wyniku tych działań, spełnia warunek p najwyższa 2 <=N 2. Oznacza to, że dysponując wynikami wcześniejszych obliczeń, możemy znaleźć nieskończenie wiele liczb pierwszych. Adaptacja algorytmu do kodu źródłowego Zapiszmy powyższy przykład w postaci kodu źródłowego w języku C. Kod prezentuje jedno z najprostszych możliwych rozwiązań. --------------ŹRÓDŁO 1-------------- 1. #include<stdio.h>//dodajemy bibliotekę we-wy 2. main() 3. { 4. int liczp[99];//tablica dla odnajdywanych liczb pierwszych 5. int illiczb,nilliczb;//zmienne zliczające ilość liczb na tablicy liczp[] 6. int liczba;//zmienna przechowująca sprawdzaną liczbę 7. liczp[1]=2;//wpisujemy na początkowe pozycje tablicy liczby pierwsze 8. liczp[2]=3;//dla N=100 9. liczp[3]=5; 10. liczp[4]=7; 11. illiczb=4;//wpisujemy do zmiennej aktualną ilość liczb na taśmie 12. nilliczb=illiczb; 13. for(liczba=8; liczba<=100; liczba++){ 14. if((liczba%liczp[1]!=0) && (liczba%liczp[2]!=0) && (liczba%liczp[3]!=0) && (liczba%liczp[4]!=0)){ 15. nilliczb++; 16. liczp[nilliczb]=liczba;//jeśli sprawdzana liczba nie jest podzielna //przez wprowadzone początkowo na tablicę 1

17. } 18. } //liczby pierwsze to zapisujemy ja na tablicy //i zwiększamy zmienną nilliczb; zmienna //illiczb (liczba dzielników) nie zmienia się 19. for(int i=1; i<=nilliczb; i++){ 20. printf("%i) %i\n",i,liczp[i]);//po skończeniu przesiewu wyświetlamy //zawartość tablicy 21. } 22. getchar(); 23. } Liczby pierwsze zapisywane są na tablicy liczp. W liniach 7-12 wpisujemy na wspomnianą tablicę liczby spełniające warunek p 2 <=N (dla N=100) oraz określamy ilość liczb zgromadzonych na tablicy przy pomocy zmiennej illiczb, a także przypisujemy tą wartość zmiennej nilliczb, która będzie zliczała ilość liczb pierwszych w czasie ich odnajdywania. Linia 14 odpowiada za sprawdzanie liczby pod względem podzielności przez, umieszczone w liniach 7-12, liczby na tablicy. Zakres sprawdzanych liczb określamy pętlą for w linii 13. Na koniec (linie 19-21) wypisujemy zgromadzone na tablicy liczby. Optymalizacja kodu wstęp do przeszukiwania kolejnych zakresów Zmodyfikujmy powyższy kod tak, aby mógł służyć do sprawdzania liczb większych od 100 na podstawie wyników wcześniejszych zakresów. Zacznijmy od linii 14. Dla N=100 mamy tylko cztery dzielniki, więc możemy pozwolić sobie na wypisanie każdego warunku oddzielnie. Ilość dzielników wzrasta jednak po sprawdzeniu każdego kolejnego zakresu. Musimy więc zmodyfikować ten fragment. Dzięki zmiennym illiczb i nilliczb wiemy ile jest liczb na tablicy. Możemy zastosować pętle for dla dzielenia przez kolejne liczby pierwsze. Spowoduje to jednak, że wykonywane będzie dzielenie zawsze tyle razy ile liczb mamy na tablicy, pomimo tego, że sprawdzana liczba okaże się podzielna przez którąś z nich. Jeśli dzielenie przez 3 lub 5 da resztę 0 to dalsze dzielenie nie jest uzasadnione. Zastosujmy więc pętle while. Dodajmy zmienną flaga która będzie regulowała działanie pętli. Określmy, że pętla będzie wykonywana gdy flaga!=0. Wartość 0 należy w tej sytuacji przypisać zmiennej flaga, gdy: - w wyniku dzielenia otrzymamy resztę 0; - wykonamy dzielenie tyle razy, ile liczb mieliśmy na tablicy przed rozpoczęciem przesiewu; Aby umożliwić sprawdzanie drugiego z powyższych warunków wprowadźmy zmienną j, która będzie zliczała wykonania pętli. Sprawdzana liczba będzie pierwsza, jeśli wykonamy pętle tyle razy ile mamy na tablicy liczb i nie otrzymamy reszty 0 w ostatnim dzieleniu. Wprowadźmy zmienną flagapoz, której zadaniem będzie zliczanie powtórzeń pętli, w których osiągnęliśmy resztę inną niż 0. Ostatecznie otrzymujemy: --------------ŹRÓDŁO 2-------------- 1. flaga=1;//zmienna warunkująca wykonywanie pętli while 2. flagapoz=0;//zmienna zliczająca ilość dzieleń z resztą inną niż 0 3. j=0;//licznik wykonania pętli while 4. while(flaga!=0) 5. { 6. j++; 7. if(j>illiczb liczba%liczp[j]==0){ 8. flaga=0;//jeśli wykonaliśmy dzielenie dla wszystkich liczb pierwszych 2

//z poprzedniego zakresu lub otrzymaliśmy resztę równą 0 //kończymy działanie pętli 9. } 10. else{ 11. flagapoz++;//jeśli nie zwiększamy zmienną flagapoz 12. } 13. } 14. if(flagapoz==illiczb){ 15. nilliczb++;//jeśli wartości zmiennych flagapoz i illiczb są takie same //oznacza to że podczas dzielenia ani razu nie otrzymaliśmy //reszty 0, co wskazuje iż liczba jest pierwsza 16. liczp[nilliczb]=liczba;//więc zapisujemy ją na tablicy 17. } 18. } Zmieńmy teraz 13 linie Źródła 1, aby wykluczyć sprawdzanie liczb parzystych, a tym samym zmniejszyć ilość powtórzeń pętli o połowę. Wystarczy zmienną, operującą pętlą, zwiększać o 2 przy każdym przejściu pętli. Musimy przy tym zacząć od liczby nieparzystej. --------------ŹRÓDŁO 3-------------- 1. for(liczba=9; liczba<=100; liczba+=2){ Dodajmy jeszcze funkcję pauza, która zatrzyma działanie programu po wyświetleniu każdych 23 linii wyników i umożliwi wyświetlenia kolejnych wyników lub wyjście z programu. Funkcja ma postać: --------------ŹRÓDŁO 4-------------- 1. void pauza() 2. { 3. char znak; 4. if(ilwierszy==24){ 5. printf("[enter] - następna strona wyników; [k] - koniec programu"); 6. if((znak=getchar())!='k'){ 7. clrscr(); 8. ilwierszy=0; 9. } 10. else{ 11. exit(0); 12. } 13. } 14. } Odwołanie do funkcji umieśćmy po wypisaniu na ekranie znalezionej liczby pierwszej. Funkcja wykorzystuje zmienną globalną ilwierszy, która powiększa swoją wartość o jeden po każdym wypisaniu wyniku. Otwieranie wyższych zakresów Podzielmy zgromadzony kod na funkcje. Zmienne ze Źródła 1 określmy jako globalne. Linie 7-11 z tego samego źródła umieśćmy w funkcji init. Z pozostałej części kodu stwórzmy funkcję szukaj i przenieśmy zmienne flaga, flagapoz oraz j do tej funkcji w postaci zmiennych lokalnych. Dołączmy jeszcze bibliotekę conio.h i zastosujmy funkcję clrscr() w procedurze init. Dodajmy jeszcze zmienne minz i maxz, którymi będziemy regulować prace pętli for w funkcji szukaj. Do całości dołączmy funkcje pauza. 3

Na koniec zmodyfikujmy sposób wyświetlania wyników. Usuńmy pętle z linii 19-21 Źródła 1, która wypisywała zawartość tablicy liczp po wyszukiwaniu liczb. Zamiast niej dodajmy printf("%i\n",liczba); po kodzie dodającym kolejną liczbę do tablicy, oraz w funkcji init kod wypisujący liczby wpisane na tablice w tejże funkcji. Ostatecznie otrzymujemy: --------------ŹRÓDŁO 5-------------- 1. #include<stdio.h> 2. #include<stdlib.h> 3. #include<conio.h>//dołączamy potrzebne biblioteki 4. long int liczp[9999999];//zwiększamy rozmiar i zakres tablicy 5. long int illiczb,nilliczb; 6. long int liczba; 7. long int minz, maxz;//dodajemy zmienne minz i maxz do obsługi pętli for //w funkcji szukaj 8. short int ilwierszy;//dodajemy zmienną ilwierszy dla funkcji pauza 9. void init(){ 10. clrscr(); 11. liczp[1]=2; 12. liczp[2]=3; 13. liczp[3]=5; 14. liczp[4]=7; 15. illiczb=4; 16. nilliczb=illiczb; 17. printf("%i\n%i\n%i\n%i\n",liczp[1],liczp[2],liczp[3],liczp[4]); 18. ilwierszy=4;//wpisujemy na tablicę początkowe liczby pierwsze dla //N=100 i wypisujemy je na ekranie 19. } 20. void pauza() 21. { 22. char znak; 23. if(ilwierszy==23){ 24. printf("[enter] - następna strona wyników; [k] - koniec programu"); 25. if((znak=getchar())!='k'){ 26. clrscr(); 27. ilwierszy=0; 28. } 29. else{ 30. exit(0); 31. } 32. } 33. } 34. void szukaj() 35. { 36. short int flaga; 37. long int flagapoz, j; 38. illiczb=nilliczb; 39. for(liczba=minz; liczba<=maxz; liczba+=2) 40. { 41. flaga=1; 42. flagapoz=0; 43. j=0; 4

44. while(flaga!=0) 45. { 46. j++; 47. if(j>illiczb liczba%liczp[j]==0){ 48. flaga=0; 49. } 50. else{ 51. flagapoz++; 52. } 53. } 54. if(flagapoz==illiczb){ 55. nilliczb++; 56. liczp[nilliczb]=liczba; 57. printf("%i\n",liczba);//jeśli liczba jest pierwsza to ją wypisujemy 58. ilwierszy++;//zwiększamy zmienną przechowującą ilość wykorzystanych //wierszy na ekranie 59. pauza();//i odwołujemy się do funkcji pauza 60. } 61. } 62. } 63. main() 64. { 65. init(); 66. minz=9; 67. maxz=100; 68. szukaj(); 69. minz=101; 70. maxz=10000; 71. szukaj(); 72. minz=10001; 73. maxz=100000000; 74. szukaj(); 75. getchar(); 76. } W liniach 66-74 określamy ramy zakresów (zmienne minz i maxz) i wywołujemy funkcję szukaj, która sprawdza liczby z kolejnych zakresów na podstawie zgromadzonych wcześniej na tablicy danych. Powyższy kod wyszukuje liczby pierwsze mniejsze od 10 8. Kolejny zakres obejmowałby liczby od 100000001 do 10 16, jednak próba sprawdzenia tak dużych liczb powoduje pojawienie się nowych problemów. Jeszcze wyższe zakresy Część liczb ze zbioru od 100000001 do 10 16 wykracza poza zakres zmiennej liczba w której są przechowywane sprawdzane wartości. Nie będzie więc możliwe wykonywanie obliczeń ani operowanie pętlą for w procedurze szukaj. Wykorzystajmy nową zmienną do operowania tą pętlą. Dodajmy też drugą pętlę for. Ujmując schematycznie uzyskamy: --------------ŹRÓDŁO 5-------------- 1. for(int X=1; X<=100000000; X++){ 2. for(int Y=1; Y<=100000000; Y++){ 3. //komendy 4. } 5. } 5

Komendy zostaną wykonane 10 8 *10 8, czyli 10 16 razy. Dla przeszukania zakresu liczb do 10 32 potrzebne będzie dodanie kolejnych pętli. Problem wykonywania obliczeń na sprawdzanej liczbie wymaga bardziej złożonego rozwiązania. Wykorzystajmy metodę, używaną w bankowości do sprawdzania poprawności numerów rachunków bankowych. Postępując zgodnie z nią, modulo z danej liczby możemy obliczyć, dzieląc sprawdzaną liczbę na części. Gdy to zrobimy obliczamy modulo z pierwszej części, a wynik dopisujemy na początku następnego fragmentu liczby. Obliczamy modulo z otrzymanej liczby i ponownie wynik umieszczamy na początku kolejnej części liczby. Na przykład: Szukamy wyniku następującego działania: 21357648 % 7 Podzielmy liczbę na części: 21 35 76 48 Obliczmy modulo z pierwszego fragmentu liczby: 21 % 7 = 0 Dopiszmy wynik na początku drugiego fragmentu i obliczmy modulo: 035 % 7 = 0 (liczymy jak 35 % 7) Powtórzmy powyższe czynności aż do uzyskania ostatecznego wyniku: 076 % 7 = 6 648 % 7 = 4 Otrzymaliśmy ostatecznie 4, więc reszta z dzielenia 21357648 przez 7 wynosi 4. Dostosujmy tę metodę do potrzeb programu i możliwości języka. Liczbę będziemy przechowywać w postaci rozbitej na tablicy sprliczba. Obliczmy na jak małe fragmenty musimy dzielić liczbę. Największa liczba pierwsza z zakresu do 10 8 to 99999989. Oznacza to, że podczas sprawdzania kolejnego zakresu największa reszta z dzielenia jaką możemy uzyskać ma wartość 99999988. Aby nie wykroczyć poza zakres zmiennej, po dopisaniu tej reszty przed fragmentem liczby musimy dzielić sprawdzaną liczbę na części jednocyfrowe. Dołączmy do programu dwie funkcje. Pierwsza z nich będzie zwiększać przechowywaną na tablicy liczbę (nadajmy tej funkcji nazwę powiększ). Druga będzie odpowiedzialna za wykonywanie obliczeń (nazwiemy ją dziel). --------------ŹRÓDŁO 6-------------- 1. void powieksz(){ 2. int wskaznik=1;//wskaźnik rządu na którym operujemy (początkowo wskazuje //na rząd jedności) 3. char flagaws=0; 4. sprliczba[wskaznik]+=2;//powiększamy rząd jedności 5. if(sprliczba[wskaznik]>10){//jeśli wartość rządu jedności przekroczy 10 //musimy ją zredukować do 1 i powiększyć //liczbę w kolejnym rzędzie 6. sprliczba[wskaznik]=1; 7. wskaznik++; 8. while(flagaws==0){ 9. sprliczba[wskaznik]=sprliczba[wskaznik]+1; 10. if(sprliczba[wskaznik]=10){ 11. sprliczba[wskaznik]=0; 12. wskaznik++; 13. }else flagaws=1; 14. }//koniec while 6

15. }//koniec if z linii 5 16. }//koniec funkcji powieksz Zmienna wskaźnik przechowuje pozycje na tablicy na której wykonywane są obliczenia. W liniach 4-6 operujemy na fragmencie liczby, który odwzorowuje rząd jedności. Dlatego zwiększamy tą pozycję o 2, dzięki czemu omijamy w czasie sprawdzania liczby parzyste (UWAGA: musimy zacząć od liczby nieparzystej). Pozostałe pozycje zwiększamy o jeden. W liniach 5. i 10. przy pomocy instrukcji if sprawdzamy czy liczby w danym rzędzie osiągnęły (lub przekroczyły w przypadku rzędu jedności) wartość 10. Jeśli tak jest musimy te pozycje zredukować i zwiększyć liczby w kolejnych rzędach. Poniżej kod drugiej ze wspomnianych wcześniej funkcji: --------------ŹRÓDŁO 6-------------- 1. void dziel(){ 2. char flagawyp=0; 3. long int reszta=0; 4. m=40; 5. reszta=0; 6. while(flagawyp==0 && m--){ 7. if(sprliczba[m]!=0)flagawyp=1; 8. }//koniec while 9. while(m>0){ 10. if(reszta!=0)reszta=(reszta*10+sprliczba[m])%liczp[j]; 11. else reszta=sprliczba[m]%liczp[j]; 12. m--; 13. }//koniec while 14. wynikmod=reszta; 15. }//koniec procedury dziel Przy pomocy pętli while w liniach 6-8, z wykorzystaniem zmiennej m, określamy od jakiej pozycji na tablicy zaczynamy operować na liczbie (pozostałe pozycje zawierają zera). Choć różnica w prędkości działania pomiędzy powyższą metodą a tą, która zastosowaliśmy wcześniej może nie być zauważalna jeśli uruchomimy program na niskich zakresach, to jednak dla liczb do 10 8 jej wykorzystywanie wydłuża znacznie czas pracy programu. Dlatego też dla początkowych zakresów będziemy stosować starą metodę. Wprowadźmy zmienną fpid, która będzie regulować z którego algorytmu ma korzystać program. Funkcja szukaj uzyskuje ostatecznie formę: --------------ŹRÓDŁO 7-------------- 1. void szukaj() 2. { 3. short int flaga; 4. long int flagapoz; 5. char flagawyp=0; 6. illiczb=nilliczb; 7. for(long int op1=minz1; op1<=maxz1; op1++) 8. { 9. for(long int op2=minz2; op2<=maxz2; op2++) 10. { 11. flaga=1; 12. flagapoz=0; 7

13. j=0; 14. liczba=liczba+2; 15. powieksz(); 16. while(flaga!=0) 17. { 18. j++; 19. if(j>illiczb){ 20. flaga=0; 21. } 22. else{ 23. if(fpid==1) dziel(); 24. else wynikmod=liczba%liczp[j]; 25. if(wynikmod==0){flaga=0;} 26. else{flagapoz++;} 27. } 28. }//koniec while 29. if(flagapoz==illiczb){ 30. nilliczb++; 31. if(fpid==1){ 32. 33. flagawyp=0; 34. for(m=49; m>=1; m--){ 35. if(sprliczba[m]!=0)flagawyp=1; 36. if(flagawyp==1)printf("%i",sprliczba[m]); 37. } 38. printf(" >fpid<\n"); 39. 40. } 41. else{ 42. printf("%i\n",liczba); 43. liczp[nilliczb]=liczba; 44. } 45. ilwierszy++; 46. pauza(); 47. } 48. } 49. } 50. } Jeśli liczba zostanie uzyskana przy użyciu funkcji powiększ i dziel, obok niej pojawi się napis >fpid< (38. linia kodu). Dodatkowo liczby w ten sposób uzyskane nie są zapisywane na tablicy. Funkcję init uzupełniamy w następujący sposób: --------------ŹRÓDŁO 8-------------- 1. void init(){ 2. clrscr(); 3. liczp[1]=2; 4. liczp[2]=3; 5. liczp[3]=5; 6. liczp[4]=7; 7. illiczb=4; 8. nilliczb=illiczb; 9. liczba=7; 8

10. sprliczba[1]=7; 11. for(int k=2; k<=50; k++){ 12. sprliczba[k]=0; 13. } 14. printf("%i\n%i\n%i\n%i\n",liczp[1],liczp[2],liczp[3],liczp[4]); 15. ilwierszy=4; 16. } Funkcja main przybiera postać: --------------ŹRÓDŁO 9-------------- 1. main() 2. { 3. init(); 4. fpid=0; 5. minz1=1; 6. maxz1=1; 7. minz2=1; 8. maxz2=45; 9. szukaj(); 10. minz1=1; 11. maxz1=1; 12. minz2=1; 13. maxz2=4949; 14. szukaj(); 15. minz1=1; 16. maxz1=1; 17. minz2=1; 18. maxz2=49994999; 19. szukaj(); 20. fpid=1; 21. minz1=1; 22. maxz1=49999999; 23. minz2=1; 24. maxz2=100000001; 25. szukaj(); 26. getchar(); 27. } Zmienne globalne programu: --------------ŹRÓDŁO 9-------------- 1. long int liczp[9999999]; 2. unsigned long int sprliczba[50]; 3. int illiczb,nilliczb; 4. unsigned long int liczba, wynikmod, j; 5. int minz1, maxz1, minz2, maxz2; 6. short int ilwierszy; 7. char m, fpid; Można jeszcze bardziej przyspieszyć działanie programu poprzez uzależnienie wywołania funkcji powieksz w linii 15. Źródła 7 od wartości zmiennej fpid i wypełnienie w odpowiedni sposób tablicy sprliczba w funkcji init. 9

Zwróćmy uwagę na wartości przypisywane zmiennym min1, max1, min2 oraz max2 przed wykonaniem każdego przesiewu. W czasie pierwszego wywołania funkcji szukaj musimy sprawdzić liczy od 1 do 100. Odrzucamy jednak liczbę 1 (bo ma tylko jeden dzielnik) i początkowe liczby pierwsze (2,3,5,7). Pomijamy także liczbę 100 (bo jest parzysta). Przeszukiwanie zaczynamy od liczby 9 (najbliższej nieparzystej po 7). Nie sprawdzamy także pozostałych parzystych, więc w istocie musimy wykonać działania na następującej ilości liczb: (99-9) / 2 = 45. W drugim przesiewie przeszukujemy liczby od 101 do 9999 (pomijamy 100 i 10000 bo są parzyste). Po odliczeniu pozostałych liczb parzystych z tego zakresu pozostaje nam: (9999-101) / 2 = 4949. Analogicznie obliczamy ilość koniecznych powtórzeń pętli dla pozostałych zakresów. Trzeba tu jednak zwrócić uwagę na rozbicie obliczonych wartości na dwie zmienne dla zakresu od 100000001 do 10 16-1. Jest to konieczne, ponieważ, jak już wyżej zostało to wspomniane, liczba 10 16-1 wykracza poza zakres zmiennej. Oczywiście podane przeze mnie wartości są tylko jedną z wielu możliwości. Można je zastąpić dowolnymi liczbami mieszczącymi się w zakresie zmiennych, których iloczyn wyniesie tyle, ile ilość koniecznych powtórzeń, jaką obliczymy dla zakresu do 10 16 według podanego wyżej schematu. Kolejne zakresy i problemy z nimi związane Wykonanie przesiewu dla następnego zakresu czyli dla liczb do 10 32 wiąże się z koniecznością rozwiązania nowych problemów. Pierwszym jest zapisanie, wykraczających poza zakresy zmiennych, liczb uzyskanych wcześniej. Można zastosować tablice wielowymiarową, gdzie każdy wiersz zawiera jedną liczbę rozbitą na fragmenty. Powstaje jednak problem wykonywania obliczeń przy pomocy tych liczb. Rozważyć należy zatem zapis uzyskanych wcześniej liczb pierwszych w postaci sumy iloczynów. Każdą liczbę pierwszą można przedstawić jako sumę wielokrotność liczb uzyskanych wcześniej. Następnie obliczenia dla wyższych zakresów wykonywać można, wykorzystując właściwości działań na liczbach. Inną metodą może być wykorzystanie hipotezy Goldbacha, zgodnie z którą każda liczba naturalna parzysta większa od 2 jest sumą dwóch liczb pierwszych. 1 Zakładając jej prawdziwość dochodzimy do prostego wniosku, że każda liczba nieparzysta większa od 2 jest sumą dwóch liczb pierwszych pomniejszoną o jeden. Tak więc można spróbować przedstawić odnalezione liczby pierwsze właśnie w takiej postaci. Nadal jednak nie mamy prostej metody wykonywania dalszych obliczeń i nade wszystko musimy pamiętać, że hipoteza ta nie została do dnia dzisiejszego jednoznacznie rozstrzygnięta, choć większość współczesnych matematyków uważa, że jest ona prawdziwa. 2 Niezależnie od tego jaki sposób zapisu i wykonywania obliczeń na odnalezionych liczbach pierwszych wybierzemy, konieczne będzie znaczne rozbudowanie algorytmu sprawdzającego liczby z kolejnych zakresów. Spowoduje to wydłużenie czasu sprawdzania, który, choć początkowo tak krótki, że, bez zatrzymania pracy programu po wyświetleniu każdej partii informacji, nie jest możliwe wychwycenie poszczególnych liczb, po wejściu do zakresu 10 8-10 16 zwiększa się do kilku (a w miarę zbliżania się do końca zakresu nawet kilkudziesięciu) sekund na odnalezienie jednej liczby. Problemem, oprócz formy zapisu liczb, jest też miejsce na ich zapis. Ilość liczb pierwszych jakie zostaną odnalezione z zakresu do 10 16 jest tak duża, że należy zastanowić się nad ich zapisem poza programem (na przykład w zewnętrznym pliku). Kłopotów związanych z przesiewem kolejnych zakresów jest znacznie więcej. Prawdopodobnie wiele nowych ujawni się w czasie pracy nad rozwiązaniem tych, które jesteśmy w stanie przewidzieć. 1 Definicja hipotezy Goldbacha na podstawie strony internetowej http://www.wikipedia.org. 2 Do tej pory udało się jedynie wykazać, że hipoteza Goldbacha jest prawdziwa dla liczb naturalnych mniejszych niż 4 10 14 informacje na podstawie strony internetowej http://www.wikipedia.org. 10

Największa odkryta dotychczas liczba pierwsza składa się z ponad 7 milionów cyfr (poprzednia zawierała ponad 6 milionów cyfr, a wcześniejsza ponad 4 miliony). Dla pierwszej osoby (lub zespołu), która odnajdzie liczbę pierwszą dłuższą niż 10 milionów cyfr, przewidziana jest nagroda 100000 dolarów (nagroda dla osoby, która jako pierwsza odnalazła liczbę pierwszą mającą więcej niż milion cyfr, wynosiła 50000 dolarów). DODATEK SPECJALNY Najmniejsza liczba pierwsza z zakresu 9 99: 2 Największa liczba pierwsza z zakresu 9 99: 97 Najmniejsza liczba pierwsza z zakresu 101 9999: 101 Największa liczba pierwsza z zakresu 101 9999: 9973 Najmniejsza liczba pierwsza z zakresu 10001 (10 8-1): 10007 Największa liczba pierwsza z zakresu 10001 (10 8-1): 99999989 Najmniejsza liczba pierwsza z zakresu (10 8 +1) (10 16-1): 100000007 Największa liczba pierwsza odnaleziona przez program, na którym oparte jest to opracowanie (program pozwala na odnalezienie wszystkich liczb pierwszych do 10 16 problemem jest rosnący czas sprawdzania liczb): 100007179 (i ciągle szuka ;) Kody źródłowe i opracowanie autorstwa Waldemara Korłub I Liceum Ogólnokształcące w Kwidzynie 11