Ham random in C

Phạm Quang Minh
(Minh172)

New Member
Moi nguoi cho hoi mot ti.

+ Minh dung random function trong C de lay mot so ngau nhien.
+ Sau day print.

Gia su nhu so ngau nhien la 100, thi sau khi minh compile lai no van
tiep tuc lap di lap lai so 100. Co ai biet tai sao lai the khong ?
Va lam the nao de fix no?
 
Số ngẫu nhiên bị lặp lại là do Random seed vẫn giữ nguyên sau mỗi lần chạy. Cho giá trị seed đó thay đổi thì sẽ tạo được các số random khác nhau

Nhét vào đây 1 cái search trên internet làm ví dụ

/* random.c - simple example of setting random number seeds with time */

/* c89 random.c -o random */

#include <stdio.h>
#include <sys/types.h>
#include <time.h>

main()
{ int i;
time_t t1;

(void) time(&t1);

srand48((long) t1); /* use time in seconds to set seed */


printf("5 random numbers (Seed = %d):\n",(int) t1);
for (i=0;i<5;++i)
printf("%d ", lrand48());
printf("\n\n"); /* flush print buffer */


/* lrand48() returns non-negative long integers
uniformly distributed over the interval (0, ~2**31)
*/
}
 
Minh bé hả, cái này đơn giản lắm, khi sử dụng function srand() thì đừng dùng fix number mà dùng time function.
ie.
srand((unsigned int) time(NULL));
còn lại thì giữ nguyên cái code của chú mày.
 
Hình như cái đấy vẫn chưa phải thực sự ngẫu nhiên, hôm trước anh nguyenhbach trong AI VN group có bảo em là có một cái thuật toán trong quyển Algorithm của Robert Sedgewick về chọn số ngẫu nhiên nhưng em vẫn chưa tìm được, mọi người ai biết thì nói cho em biết với nhé. Trong quyển Algorithm của Thomas H. Cormen phổ biến ở các LAC cũng có một phần nói về biến ngẫu nhiên này nhưng chỉ định nghĩa chung chung.

Mọi người có biết tại sao không có srand thì Compiler của C chỉ in 100 ? Em học quyển Compiler construction của Kenneth nhưng không có phần này. Em đang cần cho cái project của em nhưng search trên google chưa thấy.

Nếu mà dùng srand thì dùng như anh nguyenhbach thế này là được:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

void main( void )
{
int i;

/* Seed the random-number generator with current
time so that the numbers will be different every time
we run.
*/
srand( (unsigned)time( NULL ) );

/* Display 10 numbers. */
for( i = 0; i < 10;i++ )
printf( " %6d\n", rand() );
}

Output
6929
8026
21987
30734
20587
6699
22034
25051
7988
10104

**********************

Ngoài lề một chút, không có cách chọn số ngẫu nhiên tuyệt đối... hiểu theo nghĩa cực rộng là ngoài đời không có việc gì là hoàn toàn tình cờ >>> luật Nhân Quả trong triết học.
 
Chỉnh sửa lần cuối:
Mọi người có biết tại sao không có srand thì Compiler của C chỉ in 100

Hi Minh,

Anh nghĩ là nếu không thay đổi cái random seed thì compiler nào cũng đưa ra 1 chuỗi các số ngẫu nhiên giống nhau chứ chả riêng C. 1 số ngôn ngữ khôn hơn chắc nó tự giúp mình làm việc đó nên mình không cần để ý nữa.

Nguyên tắc (chắc mọi người cũng biết rồi) là với mỗi 1 random seed thì hàm random sẽ tạo ra 1 chuỗi (fixed) giả ngẫu nhiên tương ứng, còn làm sao cho cái seed đó cũng trở nên random thì lại phải dùng 1 số mẹo (kiểu như gán với thời gian thực hiện lệnh)

Anh đoán các thuật toán lằng nhằng về số ngẫu nhiên chung qui chỉ để có thể tạo được 1 chuỗi số tuân theo 1 phân bố nào đó, chứ còn bản chất thực sự ngẫu nhiên thì không thể nào có được nếu chỉ dùng 1 hệ đóng deterministic là máy tính.

Có lẽ có thể dùng mẹo là nối máy tính với 1 máy đo nhiệt độ ngoài trời chẳng hạn rồi lấy số thập phân của kết quả đo mà dùng làm seed cho hàm random. Hoặc nối vào máy đo tính cách phụ nữ (Cái này đảm bảo ngẫu nhiên hơn cả máy đo nhiệt độ)
 
Pham Quang Minh đã viết:
Moi nguoi cho hoi mot ti.

+ Minh dung random function trong C de lay mot so ngau nhien.
+ Sau day print.

Gia su nhu so ngau nhien la 100, thi sau khi minh compile lai no van
tiep tuc lap di lap lai so 100. Co ai biet tai sao lai the khong ?
Va lam the nao de fix no?

Trong C và một số ngôn ngữ lập trình thường phải khởi tạo bộ sinh số ngẫu nhiên (randomize) trước khi gọi hàm tạo số ngẫu nhiên (random). Em ghi lại đoạn mã này, anh thử chạy xem có khác nhau giữa các lần chạy không nhé:

#include <stdlib.h>
#include <time.h> //randomize
#include <stdio.h>

int main(int argc, char* argv[]) {
float field[100];
randomize(); //initialize random-number-generator
for(int i=0;i<100;i++) field=random(99);

for(i=0;i<100;i++) printf("%4.2f ", field);
return 1;
}
 
Chỉnh sửa lần cuối:
Random values được tạo ra dựa vào statistics concepts. Tùy chú muốn generate no dựa vào distribution nào thôi. Chẳng hạn uniform, exponential, bernoulli, binomial, geometric, .... Algorithms cho những cái này cũng không khó khăn. Viết code in C thì càng không có vấn đề. C language hình như sử dụng Bernoulli hoặc là exponential distribution cho random, không rõ lắm. Nếu không muốn sử dụng C built-in function thì có thể tự viết lấy function để generate random numbers. Nếu cần một cái ví dụ thì anh có thể giúp chú viết cái code :D lấy công rẻ thôi
 
Trong quyen C++ cua dong chi tac gia (Bistroups gi do, dai khai la ten rat quam) co dua ra may thuat toan de tim cac so ngau nhien tot hon kieu dung thong thuong. Minh dung C++ va tao ham ngau nhien theo kieu "ngay tho" nhu sau:

#include <time.h>

int main(){
//khoi dong cai ham ngau nhien, chi lam mot lan
srand(time(NULL));

rand(); /*se cho mot so nguyen (integer) ngau nhien nam tu 0 den RAND_MAX, thich dung bao nhieu lan cung duoc */

}

Con C thi minh ko biet dung, chac cung na na' nhu nhau :-D
 
Chỉnh sửa lần cuối:
Pham Quang Minh đã viết:
Hình như cái đấy vẫn chưa phải thực sự ngẫu nhiên, hôm trước anh nguyenhbach trong AI VN group có bảo em là có một cái thuật toán trong quyển Algorithm của Robert Sedgewick về chọn số ngẫu nhiên nhưng em vẫn chưa tìm được, mọi người ai biết thì nói cho em biết với nhé. Trong quyển Algorithm của Thomas H. Cormen phổ biến ở các LAC cũng có một phần nói về biến ngẫu nhiên này nhưng chỉ định nghĩa chung chung.

Hehe anh có quyển Algorithms của Robert Sedgewick dịch sang tiếng Việt đây :) mua từ hồi lớp 9 mà bây giờ còn chưa đọc hết nửa quyển :))

Trong quyển này có 2 phương pháp tạo số ngẫu nhiên là Đồng dư tuyến tính và Đồng dư cộng.
Scanner nhà anh đang teo nên không scan được.



Phương pháp đồng dư tuyến tính

Là phương pháp nổi tiếng nhất để tạo số ngẫu nhiên, đựơc sử dụng gần như độc chiếm kể từ khi D. Lehner đưa ra năm 1951. Đoạn chương trình sau tạo ra N số ngẫu nhiên cho mảng A : (quyển này dùng pascal làm ví dụ).
=================
a[0] = seed;
for i:=1 to N do a := (a[i-1]*b + 1) mod m;
==================
(trong pascal, assignment operator là := )

Với 3 hằng số seed, b và m.

Trong thuật toán này, để tạo một số ngẫu nhiên mới, ta dùng số trước đó nhân với b, cộng thêm 1 và lấy phần dư trong phép chia (kết quả) cho m. Như vậy số nhận được luoon nằm trong đoạn từ 0 đến m-1. Điều này rất thích hợp để sử dụng trong máy vi tính, bởi vì hàm mod thường dễ cài đặt: nếu chúng ta không xét đến tình huống tràn trong các phép toán số học, khi mà hầu hết các máy vi tính đều loại các bit tràn và vì vậy thực hiện rất hiệu quả phép toán mod với m bằng một số lơn hơn giá trị tối đa của một word trên máy. Hơn nữa, các số thì không thực sự ngẫu nhiên, chương trình chỉ tạo ra các số mà chúng ta hy vọng sẽ xuất hiện ngẫu nhiên cho vài tiến trình khác.

Trông thì có vẻ đơn giản, nhưng phương pháp tạo số ngẫu nhiên đồng dư tuyết tính đã là chủ đề của nhiều tập sách toán khó và chi tiêt. Chúng cung cấp cho chúng ta một số hướng dẫn trong việc chọn các hắng số seed, b và m. Vài nguyên tắc tổng quát được vận dụng, nhưng trong trường hợp này sự tổng quát khoong đủ để bảo đảm các số ngẫu nhiên tốt.

Trước hết, m nên lớn, nó có thể là giá trị tối đa của một word, nhưng cũng không cần phải hoàng toàn lớn như vậy nếu không tiện. Thông thường, chọn m là một lũy thừa của 10 hay 2 là thuận lợi. Thứ hai b không nên quá lớn hay quá nhỏ: một lựa chọn an toàn là dùng một số có ít hơn m một chữ số. Thứ ba, b nên là một hằng số tùy ý, không theo một mẫu riêng nào cả, ngoại trừ nó nên kết thúc bởi ...x21 với x chẵn, là yêu cầu cuối cùng, một đặc quyền phải được thừa nhận, nhưng nó tránh được một vài sự cố có thể xảy ra mà các phân tích toán học còn để hở.

CÁc quy luật nêu trên được phát triển bởi D.E. Knuth. Knuth chứng minh rằng các sự lựa chọn nay làm cho phương pháp đồng dư tuyến tính tẩo các số ngẫu nhiên tốt, thỏa mãn được nhiều kiểm tra thống kê phức tạp. Vấn đề có khả năng nghiêm trọng nhất, là tạo ra một chu kỳ nhỏ so với miền xác định của nó. Ví dụ như với b = 19, m = 381, seed = 0, sẽ tạo ra chuỗi 0, 1, 20, 0, 1, 20,... một chuỗi khoong ngẫu nhiên trong khoảng từ 0 đến 380.

Đáng tiếc là không phải tất cả các khó khăn như vậy đều dễ nhận ra. Vì vậy tốt nhất là nên theo các chỉ dẫn do Knuth đưa ra, để tránh được các cạm bẫy tinh tế mà ông đã khám phá được.

(còn tiếp)
 
Phương pháp đồng dư tuyến tính (cont)

Giá trị khởi động bấts kỳ nào cũng có thể sử dụng được trong việc tạo các số ngẫu nhiên mà không có ảnh hưởng gì đặc biết (dĩ nhiên là ngoại trừ việc giá trị khởi động khác nhau thì tạo thành các chuỗi ngẫu nhiên khác nhau). Thườngthif không cần phải lưu tữ cả chuỗi như chương trình nêu trên. Ngược lại chúng ta đơn giản giữ lại trong một biến toàn cục a, khởi động với một giá trị nào đó, rồi cập nhật bằng phép tính a := (a*b + 1) mod m.

Trong Pascal (và nhiều ngôn ngữ lập trình khác), chúng ta còn một bước nữa mới có thể thực hiện được, bởi vì chúng ta không được phép bỏ qua tình trạng trành : nó được định nghĩa là một trường hợp lỗi mà có thể tạo ra các kết quả không dự đoán được. Giả sử rằng máy vi tính của chúng ta có word 32 bit, và chúng ta chọn m = 100000000, b = 31415821, và khởi động a = 1234567. Tất cả các giá trị này đều nhỏ hơn giá trị tối đa của một số nguyên, nhưng phép toán đầu tiên a*b + 1 đã làm tràn. Phần của kết quả đã gây ra sự tràn là thì không có quan hệ với sự tính toán của chúng ta bởi vì chúng ta chỉ quân tâm đến 8 ký số sau. Một thủ thuật để loại bỏ sự trành là phân nhỏ phép nhân ra làm nhiều phần. Để nhân p và q chúng ta viết
p = 10^4 * p1 + p0 và q = 10^4 * q1 + q0
Do đó kết quả là :

pq = (10^4 * p1 + p0)(10^4 * q1 + q0) = 10^8 * p1q1 + 10^4(p1q0 + p0q1) + p0q0

Bây giờ chúng ta chỉ muốn 8 ký số cho kết quả, vì thế chúng ta có thể bỏ qua số hạng đâu tiên (10^8 * p1q1) và bỏ qua 4 ký số đầu của số hạng thứ 2 (10^4(p1q0 + p0q1)) Điều này dẫn đến chương trình ở trang sau.

Hàm mult trong chương trình này tính (p*q mod m), không bị trành kh m nhỏ hơn 1/2 giá trị số nguyên tối đa. Kỹ thuật này hiển nhiên có thể đáp ứng với các giá trị m1 khác theo nguyên tắc m = m1 + m1.

Mã:
Program random (input, output);;
    const m = 100000000; m1 = 10000; b = 3141581;
    var i, a, N:integer;
    function mult(p,q:integer):integer;
        var p1,p0,q1,q0 : integer;
        begin
            p1 := p div m1; p0 = p mod m1;
            q1 := q div m1; q0 = q mod m1;
            mult := (((p0*q1 + p1*q0) mod m1) * m1 + p0*q0) mod m;
        end;
    function random:integer;
        begin
            a := (mult(a,b) + 1) mod m;
            random := a;
        end;

begin
    readln(N,a);
    for i:=1 to N do writeln(random);
end.

Khi chạy chương trình với dữ liệu nhập N = 10 và q = 1234567, chương trình tạo được 10 số như sau : 35884508, 80001069, 63512650, 43635651, 1034472, 87181513, 6917174, 209855, 67115956, 59939877. Trong các số này, có vài sự không ngẫu nhiên: ví dụ, các ký số sau cùng (hàng đơn vị) xoay vòng qua các số từ 0 đến 9. Dễ dàng chứng minh được điều này sẽ xảy ra từ công thức. Nói chung các ký số bên phải không thật sự ngẫu nhiên, thực tế đó là nguồn gốc của các lỗi thông thường và quan trọng trong việc sử dụng phương pháp tạo số ngẫu nhiên đồng dư tuyến tính.

(to be cont.)
 
Phương pháp đồng dư tuyến tính (cont)

Chương trình sau đây là một chương trình dở, tạo các số ngẫu nhiên trong giới hạn [0, r-1]:

Mã:
function randombad(r : integer) : integer;
begin
    a := (mult(b,a) + 1) mod m;
    randombad := a mod r;
endl;

Các ký số không ngẫu nhiên bên phải là các ký số duy nhất được sử dụng nên chuỗi kết quả chỉ có được ít thuộc tính mong muốn. Vấn đề này có thể dễ dàng khắng phục bắng cách dùng các ký số bên trái thay thế.

Chúng ta muốn tính một con số giữa 0 và r-1 bằng công thức a*r div m, nhưng một lần nữa lại phải tránh tình huống tràn như trong phần cài đặt sau.

Mã:
function randomint(r : integer) : interger;
begin
    a := (mult(a,b) + 1) mod m;
    randomint : = ((a div m1) * r) div m1;
endl;

Một kỹ thuật thông thường khác là tạo số thực ngẫu nhiên giữa 0 và 1 bằng cách xem các số tạo như trên là phần thập phân với chấm thập phân ở bên trái. Điều này có thể cài được dễ dàng bằng cách trả về giá trị thực (real) a/m thay vì số nguyên a. Khi đó người sử dụng có thể nhận được một số nguyên trong giới hạn [0,r) bằng cách đơn giản nhân giá trị này với r và cắt lấy phần nguyên của kết quả. Hay có thể nói một số thực ngẫu nhiên giữa 0 và 1 có thể mới chính là các số cần thiết.

./.

PS: Còn cái phương pháp đồng dư cộng nữa, minh bé có cần thì anh type nốt :)
 
QTD: Theo em thì tính cách phụ nữ không random một tí nào :D (ăn quà, shopping,...) ngày nào cũng như ngày nào :D.....

Nhiệt độ thì vẫn là curve, nhiệt độ trong một ngày rất stable, nhiệt độ trung bình của tháng và của mùa cũng ít đổi >>> Không nên dùng cái này.

PQL: Bác cứ tiếp đi, em vẫn đang đọc nhưng chưa hiểu lắm nên chưa có ý kiến gì.
 
Hàm srand( (unsigned)time( NULL ) ); của bác AT dùng cực kỳ chuối

cái chương trình của em chạy được khoảng 20%. 80% còn lại chương trình chết vì chọn số vẫn chưa thực sự ngẫu nhiên.... hi'c :(... Em sẽ thử cách bác Linh đưa.

Thêm một câu hỏi nữa:

- Nếu pick phải một số "xấu" compiler chỉ chạy 5 giây sau khi lặp lại quá nhiều lần thì dừng. Nếu muốn bắt nó chạy cho tới khi ra kết quả cuối cùng thì làm thế nào? (hoặc quá 24 giờ chưa ra kết quả thì mới dừng ).
 
- Nếu pick phải một số "xấu" compiler chỉ chạy 5 giây sau khi lặp lại quá nhiều lần thì dừng. Nếu muốn bắt nó chạy cho tới khi ra kết quả cuối cùng thì làm thế nào? (hoặc quá 24 giờ chưa ra kết quả thì mới dừng ).

Anh nghĩ là dùng srand() 1 phát, sau đó chỉ dùng rand thôi, đến lúc nào cảm thấy bị lặp lại thì lại srand() phát nữa

Nhiệt độ thì vẫn là curve, nhiệt độ trong một ngày rất stable, nhiệt độ trung bình của tháng và của mùa cũng ít đổi >>> Không nên dùng cái này.

:) Anh không nói là dùng nhiệt độ, mà dùng 2 chữ số thập phân của phép đo nhiệt độ, kiểu như -19.35oC thì lấy số 35 mà dùng. Không muốn nối vào máy đo nhiệt độ hoặc cắm cái đo của mình vào chị em thì tìm cách mà đếm số IP packets chạy ra vào máy mình, cái đó cũng random phết. Nói chung những gì có interaction với người dùng là random.

Hình như Windows là multithreading, nên các lệnh chạy không hoàn toàn theo đúng trật tự mình đặt ra (ví dụ đang chạy lại lôi ảnh sex ra xem thì chương trình chạy chậm hơn), vì thế lấy srand theo time cũng tương đối random (Với điều kiện lúc chạy lôi ảnh ra mà xem)
 
Quách Tung Dương đã viết:
Hình như Windows là multithreading, nên các lệnh chạy không hoàn toàn theo đúng trật tự mình đặt ra (ví dụ đang chạy lại lôi ảnh sex ra xem thì chương trình chạy chậm hơn), vì thế lấy srand theo time cũng tương đối random (Với điều kiện lúc chạy lôi ảnh ra mà xem)

Chuẩn chuẩn, srand của thằng C quá cũ rồi, từ thời máy tính còn chậm, bây giờ máy quá nhanh, giống như lỗi division by zero của Pascal. Chạy vài giây là bị stack overflow :(.
 
Dùng máy tính thì ko thể cho ra hàm "thực sự ngẫu nhiên đâu", kiểu gì thì cũng chỉ là pseudo-random thoi. Em định dùng sỗ ngẫu nhiên để làm gì thế? Nếu ko quan trọng lắm thì cứ lấy curent time làm seed là được rồi.
(Muốn lấy số ngẫu nhiên ngoài đời thì cứ ngồi tung đồng xu ý :D, ra mặt sấp là 0, ngửa là 1, cứ thế tung mười sáu lần thì được một số random 16 bit, hehehehe). À có khi như thế lại là cách tạo seed ngẫu nhiên đấy :). Mỗi lần chạy chường trình thì nạp cho nó cái biến là "seed", và mình có thể tạo seed bằng tay theo kiểu ... tung đồng xu :).
 
Back
Bên trên