Ευστρατιάδης Ευστράτιος Γεώργιος ΑΕΜ: 4261 2 ο σετ ασκήσεων Υπολ.Μαθ.2 Κατασκεύασα και χρονομέτρησα ρουτίνες που δίνουν τυχαίους αριθμούς, οι οποίες χρησιμοποιούν διαφόρων τύπων αριθμητικές (int, unsigned int, short int, unsigned short int), με σκοπό την παράλειψη του υπολοίπου της διαίρεσης από τον κώδικα, αφού θα γίνεται αυτόματα λόγω του περιορισμού χωρητικότητας του κάθε τύπου μεταβλητής (2 32 για 4 byte και 2 16 για 2 byte τύπου short). Παράλληλα χρονομέτρησα και μια εικονική μηχανή παραγωγής τυχαίων αριθμών, για να έχω μια τιμή αναφοράς σχετικά με τον απαιτούμενο χρόνο λειτουργίας όταν δεν γίνονται αριθμητικές πράξεις από τη μηχανή παραγωγής τυχαίων αριθμών. Οι απαιτούμενοι χρόνοι φαίνονται παρακάτω (για περίπου 3 10 8 αριθμούς από κάθε μηχανή): Ο πηγαίος κώδικας C++: 1 #include <iostream> 2 #include <fstream> 3 #include <ctime> 4 5 using namespace std; 6 const clock_t begin_time = clock(); 7 float timecounter,xfloat; 8 double xdouble; 9 int TEST_TIMES=5000; 10 11 unsigned random1(unsigned seed)//massen 12 { 13 return 1812433253*seed+1; 14 } 15 16 unsigned random1a(int seed)//massen 17 { 18 return (1812433253*seed+1);//%4294967296; 19 } 20 21 unsigned short random2(unsigned short seed)//test 22 { 23 return 137*seed+187; 24 } 25 26 short random2a(short seed)//test2 27 { 28 return 137*seed+187; 29 } 30
31 unsigned random3(unsigned seed)//numerical recipes 32 { 33 return 1664525*seed+1013904223; 34 } 35 36 unsigned random4(unsigned seed)//borland C/C++ 37 { 38 return 22695477*seed+1; 39 } 40 41 void dummyrandom(int dummyseed) 42 { 43 //return dummyseed; 44 } 45 46 int main() 47 { 48 cout << "Testing CPU speed." << endl; 49 ofstream out; 50 int x0=1989;//init. seed 51 // timing without calculations 52 timecounter = float( clock () - begin_time ) / CLOCKS_PER_SEC; 53 for(int manytimes=0;manytimes<test_times;manytimes++){ 54 for(int i=0;i<65536;i++){ //2^16 55 dummyrandom(x0); 56 } 57 } 58 cout<<"baseline timing: "<<float(clock()-begin_time)/clocks_per_sec-timecounter<<endl; 59 60 // timing with calculations 1 61 unsigned x1=1989; 62 timecounter = float( clock () - begin_time ) / CLOCKS_PER_SEC; 63 for(int manytimes=0;manytimes<test_times;manytimes++){ 64 for(int i=0;i<65536;i++){ //2^16 65 x1=random1(x1); 66 xdouble=(double)x1/4294967295.d; 67 } 68 } 69 cout<<"unsigned timing: "<<float(clock()-begin_time)/clocks_per_sec-timecounter<<endl; 70 71 // timing with calculations 1a 72 timecounter = float( clock () - begin_time ) / CLOCKS_PER_SEC; 73 for(int manytimes=0;manytimes<test_times;manytimes++){ 74 for(int i=0;i<65536;i++){ //2^16 75 x1=random1a(x1); 76 xdouble=(double)x0/4294967295.d; 77 } 78 } 79 cout<<"signed timing: "<<float(clock()-begin_time)/clocks_per_sec-timecounter<<endl; 80 81 // timing with calculations 2 82 unsigned short x2=1989; 83 timecounter = float( clock () - begin_time ) / CLOCKS_PER_SEC; 84 for(int manytimes=0;manytimes<test_times;manytimes++){ 85 for(int i=0;i<65536;i++){ //2^16 86 x2=random2(x2); 87 xfloat=(float)x2/65535.f; 88 } 89 } 90 cout<<"ushort timing: "<<float(clock()-begin_time)/clocks_per_sec-timecounter<<endl; 91 92 // timing with calculations 2a 93 short x2a=1989; 94 timecounter = float( clock () - begin_time ) / CLOCKS_PER_SEC; 95 for(int manytimes=0;manytimes<test_times;manytimes++){ 96 for(int i=0;i<65536;i++){ //2^16 97 x2a=random2a(x2a); 98 xfloat=(float)x2a/65535.f; 99 } 100 } 101 cout<<" Short timing: "<<float(clock()-begin_time)/clocks_per_sec-timecounter<<endl; 102 //return 0; 103 104 105 out.precision(12);
106 107 unsigned xx1=1989; 108 out.open("random1a.txt"); 109 for(int i=0;i<65536;i++){ //2^16 110 out<<(double)(xx1=random1a(xx1))/4294967295.d << endl; 111 } 112 out.close(); 113 114 unsigned short xx2=1989; 115 out.open("random2.txt"); 116 for(int i=0;i<65536;i++){ //2^16 117 out<<(double)(xx2=random2(xx2))/65535.d << endl; 118 } 119 out.close(); 120 121 return 0; 122 } Όπως φαίνεται στις γραμμές 107-119, παράγω 2 ακολουθίες 2 16 τυχαίων αριθμών, μια με Μ=2 32 και μία με Μ=2 16. Τους αριθμούς αυτούς τους εισάγω στο Mathematica για να ελέγξω την τυχαιότητά τους. Και οι δύο ακολουθίες φαίνεται να πληρούν τα κριτήρια τυχαιότητας με βάση τους μέσους όρους των ροπών. Ειδικά αν πάρουμε όλα τα σημεία της δεύτερης ακολουθίας, οι τιμές για τους μέσους όρους ροπών ταιριάζουν σχεδόν απόλυτα με τις θεωρητικά αναμενόμενες. Αυτό συμβαίνει λόγω της περιοδικότητας των αποτελεσμάτων αυτής της μεθόδου. Οι αριθμοί της δεύτερης ακολουθίας φαίνεται να έχουν τέλεια τυχαιότητα με αυτά τα κριτήρια, επειδή εμφανίζονται όλοι οι αριθμοί από το 0 μέχρι το 1, με σταθερό βήμα 2-16 : Αντίθετα, οι αριθμοί της πρώτης ακολουθίας δεν είναι τόσο «καλά τυχαίοι», με αυτά τα κριτήρια:
Αν όμως σχηματίσουμε το σύνολο των σημείων {x i, x i+1 } (με i από 0 έως 2 16-2) και τα σχεδιάσουμε στο επίπεδο, φαίνεται ότι τα σημεία της πρώτης ακολουθίας γεμίζουν ομοιόμορφα το επίπεδο, ενώ της δεύτερης δεν καλύπτουν όλο το επίπεδο, αλλά μόνο ένα σύνολο ευθειών: (Μ=2 32 ) (Μ=2 16 ) Αυτό δεν σημαίνει βέβαια ότι τα σημεία της πρώτης ακολουθίας γεμίζουν πυκνά το επίπεδο, όταν το πλήθος των σημείων τείνει στο άπειρο. Απλά η περιοδικότητα είναι 2 16 φορές μεγαλύτερη και δεν γίνεται άμεσα αντιληπτή.
Ισοδύναμα, υπολογίζω το λόγω των σημείων που βρίσκονται εντός κυκλικού τομέα 90 ο, του οποίου οι δύο ακτίνες ταυτίζονται με κάθετες πλευρές τετραγώνου (βλ. σχήμα) και λύνω ως προς π. Ο πηγαίος κώδικας C++: 1 #include <iostream> 2 #include <ctime> 3 #include <cmath> 4 5 using namespace std; 6 const clock_t begin_time = time(null); 7 8 9 unsigned rng(int seed) 10 { 11 return (1812433253*seed+1); 12 } 13 14 int main() 15 { 16 cout << "Calculating Pi." << endl; 17 cout.precision(12); 18 double pi; 19 20 unsigned x=begin_time%123456; 21 unsigned y=1989+begin_time%123456; 22 int counter; 23 int imax=100000000; 24 25 for(int j=10;j<=imax;j*=10){ 26 counter=0; 27 for(int i=0;i<j;i++){ 28 x=rng(x); 29 y=rng(y); 30 if((((double)x/4294967295.d)*((double)x/4294967295.d)+ 31 ((double)y/4294967295.d)*((double)y/4294967295.d))<1.d){ 32 counter++; 33 } 34 } 35 pi=((double)(counter))/((double)j)*4.d; 36 cout<<"with "<<j<<" random points:"<<endl; 37 cout<<"calculated Pi: "<<pi<<endl; 38 cout<<"error%: "<<fabs(100*(pi-acos(-1)))<<endl<<endl; 39 } 40 41 42 return 0; 43 }
Τα αποτελέσματα: Βλέπουμε ότι ο χρόνος εκτέλεσης έχει μικρή εξάρτηση από το πλήθος των τυχαίων αριθμών που χρησιμοποιούμε για την παραγωγή τυχαίων αριθμών από κανονική κατανομή. Το πρόγραμμα καθυστερεί κυρίως στην εξαγωγή των αριθμών σε αρχείο, παρά στην παραγωγή των τυχαίων αριθμών.
Ο πηγαίος κώδικας: 1 #include <iostream> 2 #include <ctime> 3 #include <fstream> 4 5 using namespace std; 6 unsigned int xseed=1989; 7 const clock_t begin_time = clock(); 8 9 double rnd() 10 { 11 xseed=(1812433253*xseed+1); 12 return (double)xseed/4294967295.d; 13 } 14 15 double rndgauss1(int ipoint) 16 { 17 double rndgauss1=0; 18 for(int n=1;n<=ipoint;n++){ 19 rndgauss1 = rndgauss1+rnd(); 20 } 21 rndgauss1=rndgauss1-ipoint/2.d; 22 return rndgauss1; 23 } 24 25 int main() 26 { 27 ofstream out; 28 int points=200000; 29 float timer=float(clock()-begin_time)/clocks_per_sec; 30 cout<<"seconds required to export 200k numbers from normal PDF,\n"<< 31 "using 24,12,6 and 3 random numbers from uniform PDF:"<<endl; 32 out.open("gauss24.txt"); 33 for(int i=0;i<=points;i++) out<<rndgauss1(24)<<endl; 34 out.close(); 35 36 cout<<(float(clock()-begin_time)/clocks_per_sec)-timer<<endl; 37 timer=float(clock()-begin_time)/clocks_per_sec; 38 out.open("gauss12.txt"); 39 for(int i=0;i<=points;i++) out<<rndgauss1(12)<<endl; 40 out.close(); 41 42 cout<<float(clock()-begin_time)/clocks_per_sec-timer<<endl; 43 timer=float(clock()-begin_time)/clocks_per_sec; 44 out.open("gauss6.txt"); 45 for(int i=0;i<=points;i++) out<<rndgauss1(6)<<endl; 46 out.close(); 47 48 cout<<float(clock()-begin_time)/clocks_per_sec-timer<<endl; 49 timer=float(clock()-begin_time)/clocks_per_sec; 50 out.open("gauss3.txt"); 51 for(int i=0;i<=points;i++) out<<rndgauss1(3)<<endl; 52 out.close(); 53 54 cout<<float(clock()-begin_time)/clocks_per_sec-timer<<endl; 55 56 return 0; 57 } Κάνουμε το ιστόγραμμα στο Mathematica, το οποίο έχει έτοιμη ρύθμιση για κανονικοποιημένα ιστογράμματα. Παρακάτω φαίνονται τα ιστογράμματα από αριθμούς που παρήχθησαν χρησιμοποιώντας 24, 12, 6 και 3 Τ.Α. Βλέπουμε ότι δεν υπάρχει μεγάλη απόκλιση από την κανονική κατανομή, ακόμα και για μικρότερο πλήθος Τ.Α.. Με 3 Τ.Α., στα σημεία στο μέσον και στις ουρές της κατανομής αντιστοιχεί μικρότερη πιθανότητα από όση θα έπρεπε.
Η διασπορά παίρνει τις τιμές που αναμέναμε: 2, 1, 0.5 και 0.25. Η μέση τιμή παραμένει διορθωμένη στο μηδέν από τη ρουτίνα κατασκευής των τυχαίων αριθμών.
Ο πηγαίος κώδικας: 1 #include <iostream> 2 #include <cmath> 3 #include <fstream> 4 5 6 using namespace std; 7 unsigned int xseed=1989; 8 9 class double2{ 10 public: 11 double d1,d2; 12 }; 13 14 double rnd() 15 { 16 xseed=(1812433253*xseed+1); 17 return (double)xseed/4294967295.d; 18 } 19 20 double2 rndgauss2() 21 { 22 double2 result; 23 double xi = -log(1.-rnd()); 24 double radius = sqrt(2.d*xi); 25 double theta = 2.*3.141592654*rnd(); 26 double gauss1 = radius*cos(theta); 27 double gauss2 = radius*sin(theta); 28 result.d1=gauss1; 29 result.d2=gauss1; 30 return result; 31 } 32 33 int main() 34 { 35 ofstream out; 36 int points=200000; 37 38 out.open("gauss_2.txt"); 39 for(int i=0;i<=points;i++) out<<rndgauss2().d1<<"\n"<<rndgauss2().d2<<"\n"; 40 out.close(); 41 42 return 0; 43 } Πράγματι παρήχθησαν τυχαίοι αριθμοί από την κανονική κατανομή με μέση τιμή μηδέν και διασπορά 1: