Tất cả các câu trả lời cho đến nay đều sai về mặt toán học. Việc trả về rand() % N
không đồng nhất đưa ra một số trong phạm vi [0, N)
trừ khi N
chia độ dài của khoảng mà rand()
trả về (tức là lũy thừa của 2). Hơn nữa, người ta không biết liệu các modul rand()
có độc lập hay không: có thể chúng đi 0, 1, 2, ...
, điều này đồng nhất nhưng không phải ngẫu nhiên. Giả thiết duy nhất có vẻ hợp lý là rand()
đưa ra phân phối Poisson: bất kỳ hai khoảng con không trùng lặp nào có cùng kích thước đều có khả năng xảy ra như nhau và độc lập. Đối với một tập hợp các giá trị hữu hạn, điều này ngụ ý một phân phối đồng đều và cũng đảm bảo rằng các giá trị của rand()
được phân tán độc đáo.
Điều này có nghĩa là cách đúng duy nhất để thay đổi phạm vi của rand()
là chia nó thành các hộp; ví dụ: nếu RAND_MAX == 11
và bạn muốn một phạm vi 1..6
, bạn nên gán {0,1}
cho 1,{2,3}
cho 2, v.v. Đây là những khoảng rời rạc, có kích thước bằng nhau và do đó được phân bố đồng đều và độc lập.
Đề xuất sử dụng phép chia dấu phẩy động là hợp lý về mặt toán học nhưng có vấn đề về làm tròn về nguyên tắc. Có lẽdouble
là độ chính xác đủ cao để làm cho nó hoạt động; có lẽ không. Tôi không biết và tôi không muốn phải tìm ra nó; trong mọi trường hợp, câu trả lời là phụ thuộc vào hệ thống.
Cách đúng là sử dụng số học số nguyên. Đó là, bạn muốn một cái gì đó như sau:
#include <stdlib.h> // For random(), RAND_MAX
// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
unsigned long
// max <= RAND_MAX < ULONG_MAX, so this is okay.
num_bins = (unsigned long) max + 1,
num_rand = (unsigned long) RAND_MAX + 1,
bin_size = num_rand / num_bins,
defect = num_rand % num_bins;
long x;
do {
x = random();
}
// This is carefully written not to overflow
while (num_rand - defect <= (unsigned long)x);
// Truncated division is intentional
return x/bin_size;
}
Vòng lặp là cần thiết để có được một phân phối hoàn toàn đồng đều. Ví dụ, nếu bạn được cung cấp các số ngẫu nhiên từ 0 đến 2 và bạn chỉ muốn các số từ 0 đến 1, bạn cứ tiếp tục kéo cho đến khi không nhận được số 2; không khó để kiểm tra xem điều này cho kết quả 0 hoặc 1 với xác suất bằng nhau. Phương pháp này cũng được mô tả trong liên kết mà không có trong câu trả lời của họ, mặc dù được mã hóa khác nhau. Tôi đang sử dụng random()
hơn rand()
là vì nó có phân phối tốt hơn (như trang người đàn ông đã lưu ý chorand()
).
Nếu bạn muốn nhận các giá trị ngẫu nhiên nằm ngoài phạm vi mặc định [0, RAND_MAX]
, thì bạn phải làm điều gì đó khó khăn. Có lẽ thích hợp nhất là để xác định một chức năng random_extended()
mà kéo n
bit (sử dụng random_at_most()
) và lợi nhuận trong [0, 2**n)
, và sau đó áp dụng random_at_most()
với random_extended()
ở vị trí của random()
(và 2**n - 1
thay RAND_MAX
) để kéo một giá trị ngẫu nhiên ít hơn 2**n
, giả sử bạn có một kiểu số có thể chứa ví dụ một giá trị. Cuối cùng, tất nhiên, bạn có thể nhận được các giá trị đang [min, max]
sử dụng min + random_at_most(max - min)
, bao gồm cả các giá trị âm.