Tại sao rand ()% 6 được thiên vị?


109

Khi đọc cách sử dụng std :: rand, tôi đã tìm thấy mã này trên cppreference.com

int x = 7;
while(x > 6) 
    x = 1 + std::rand()/((RAND_MAX + 1u)/6);  // Note: 1+rand()%6 is biased

Điều gì sai với biểu thức bên phải? Đã thử nó và nó hoạt động hoàn hảo.


24
Lưu ý rằng nó thậm chí còn tốt hơn để sử dụng std::uniform_int_distributioncho xúc xắc
Caleth

1
@Caleth Vâng, chỉ để hiểu tại sao mã này là 'sai' ..
yO_

15
Đã thay đổi "là sai" thành "được thiên vị"
Cubbi

3
rand()quá tệ trong các triển khai điển hình, bạn cũng có thể sử dụng xkcd RNG . Vì vậy, nó sai bởi vì nó sử dụng rand().
CodesInChaos

3
Tôi đã viết điều này (tốt, không phải bình luận - đó là @Cubbi) và những gì tôi nghĩ vào thời điểm đó là câu trả lời của Pete Becker đã giải thích. (FYI, về cơ bản đây là thuật toán giống như của libstdc ++ uniform_int_distribution.)
TC

Câu trả lời:


136

Có hai vấn đề với rand() % 6( 1+không ảnh hưởng đến cả hai vấn đề).

Đầu tiên, như một số câu trả lời đã chỉ ra, nếu các bit thấp của rand()không đồng nhất một cách thích hợp, thì kết quả của toán tử phần dư cũng không đồng nhất.

Thứ hai, nếu số giá trị khác biệt được tạo ra rand()không phải là bội số của 6, thì phần còn lại sẽ tạo ra nhiều giá trị thấp hơn giá trị cao. Điều đó đúng ngay cả khi rand()trả về các giá trị được phân phối hoàn hảo.

Như một ví dụ cực đoan, hãy giả sử điều đó rand()tạo ra các giá trị được phân phối đồng đều trong phạm vi [0..6]. Nếu bạn xem phần còn lại cho các giá trị đó, khi rand()trả về một giá trị trong phạm vi [0..5], phần còn lại sẽ tạo ra các kết quả được phân phối đồng nhất trong phạm vi [0..5]. Khi rand()trả về 6, rand() % 6trả về 0, giống như khi rand()trả về 0. Vì vậy, bạn nhận được một phân phối với số 0 nhiều gấp đôi so với bất kỳ giá trị nào khác.

Thứ hai là vấn đề thực sự với rand() % 6.

Cách để tránh vấn đề đó là loại bỏ các giá trị có thể tạo ra các bản sao không đồng nhất. Bạn tính bội số lớn nhất của 6 nhỏ hơn hoặc bằng RAND_MAXvà bất cứ khi nào rand()trả về giá trị lớn hơn hoặc bằng bội số đó, bạn sẽ từ chối nó và gọi lại `rand (), nhiều lần nếu cần.

Vì thế:

int max = 6 * ((RAND_MAX + 1u) / 6)
int value = rand();
while (value >= max)
    value = rand();

Đó là một cách triển khai khác của mã được đề cập, nhằm mục đích hiển thị rõ ràng hơn những gì đang xảy ra.


2
Tôi đã hứa với ít nhất một người thường xuyên trên trang web này sẽ viết một bài báo về vấn đề này nhưng tôi nghĩ rằng việc lấy mẫu và từ chối có thể làm mất đi những khoảnh khắc cao; ví dụ: lạm phát quá mức phương sai.
Bathsheba

30
Tôi đã vẽ một biểu đồ về mức độ thiên vị mà kỹ thuật này giới thiệu nếu rand_max là 32768, nó nằm trong một số triển khai. ericlippert.com/2013/12/16/…
Eric Lippert

2
@Bathsheba: đúng là một số hàm từ chối có thể gây ra điều này, nhưng từ chối đơn giản này sẽ biến đổi một IID đồng nhất thành một phân phối IID đồng nhất khác. Không có bit nào được chuyển sang, quá độc lập, tất cả các mẫu sử dụng cùng một loại bỏ nên giống hệt nhau và nhỏ để thể hiện tính đồng nhất. Và các mômen cao hơn của một biến ngẫu nhiên tích phân đồng nhất được xác định đầy đủ bởi phạm vi của nó.
MSalters

4
@MSalters: Câu đầu tiên của bạn đúng với trình tạo thực , không nhất thiết đúng với trình tạo giả. Khi tôi nghỉ hưu, tôi sẽ viết một bài báo về điều này.
Bathsheba

2
@Anthony Hãy suy nghĩ về mặt xúc xắc. Bạn muốn một số ngẫu nhiên từ 1 đến 3 và bạn chỉ có một con xúc sắc 6 mặt tiêu chuẩn. Bạn có thể nhận được điều đó bằng cách trừ 3 nếu bạn quay được 4-6. Nhưng thay vào đó, giả sử bạn muốn một số trong khoảng từ 1 đến 5. Nếu bạn trừ đi 5 khi cuộn số 6, thì bạn sẽ nhận được số 1 nhiều gấp đôi bất kỳ số nào khác. Về cơ bản đó là những gì mã cppreference đang làm. Điều chính xác cần làm là cuộn lại 6s. Đó là những gì Pete đang làm ở đây: chia con súc sắc để có cùng một số cách cuộn từng số và cuộn lại bất kỳ số nào không phù hợp với các vạch chia chẵn
Ray

19

Có những chiều sâu ẩn ở đây:

  1. Việc sử dụng nhỏ utrong RAND_MAX + 1u. RAND_MAXđược định nghĩa là một intkiểu và thường là kiểu lớn nhất có thể int. Hành vi của RAND_MAX + 1sẽ không được xác định trong các trường hợp như bạn đang làm tràn một signedloại. Viết 1ulực lượng chuyển đổi kiểu RAND_MAXthành unsigned, do đó, loại bỏ tràn.

  2. Việc sử dụng % 6 can (nhưng trên mọi cách triển khai std::randtôi đã thấy thì không ) đưa ra bất kỳ sai lệch thống kê bổ sung nào ở trên và ngoài phương án thay thế được trình bày. Những trường hợp % 6nguy hiểm như vậy là những trường hợp mà bộ tạo số có đồng bằng tương quan ở các bit thứ tự thấp, chẳng hạn như một triển khai của IBM khá nổi tiếng (trong C) rand, tôi nghĩ, những năm 1970 đã lật các bit cao và thấp thành "một Hưng thịnh". Một xem xét thêm là 6 là rất nhỏ cf. RAND_MAX, vì vậy sẽ có một hiệu ứng tối thiểu nếu RAND_MAXkhông phải là bội số của 6, mà nó có thể không phải là.

Kết luận, những ngày này, do tính dễ kiểm soát của nó, tôi sẽ sử dụng % 6. Nó không có khả năng tạo ra bất kỳ sự bất thường thống kê nào ngoài những điều được giới thiệu bởi chính bộ tạo. Nếu bạn vẫn còn nghi ngờ, hãy kiểm tra trình tạo của bạn để xem liệu nó có các thuộc tính thống kê thích hợp cho trường hợp sử dụng của bạn hay không.


12
% 6tạo ra một kết quả chệch bất cứ khi nào số lượng các giá trị phân biệt được tạo ra rand()không phải là bội số của 6. Nguyên tắc lỗ chim bồ câu. Đúng là sai lệch nhỏ khi RAND_MAXlớn hơn nhiều so với 6, nhưng nó ở đó. Và đối với phạm vi mục tiêu lớn hơn, tất nhiên, hiệu ứng sẽ lớn hơn.
Pete Becker

2
@PeteBecker: Thật vậy, tôi nên nói rõ điều đó. Nhưng lưu ý rằng bạn cũng có thể bị chim bồ câu kêu khi phạm vi mẫu tiếp cận RAND_MAX, do hiệu ứng cắt ngắn phép chia số nguyên.
Bathsheba

2
@Bathsheba không phải hiệu ứng cắt ngắn đó dẫn đến kết quả lớn hơn 6 và do đó trong một lần thực hiện lặp lại toàn bộ hoạt động sao?
Gerhardh

1
@Gerhardh: Đúng. Trong thực tế, nó dẫn chính xác đến kết quả x==7. Theo lẽ thường, bạn chia phạm vi [0, RAND_MAX]thành 7 subranges, 6 subranges cùng kích thước và một subrange nhỏ hơn ở cuối. Kết quả từ dải con cuối cùng bị loại bỏ. Rõ ràng là bạn không thể có hai phạm vi phụ nhỏ hơn ở cuối theo cách này.
MSalters

@MSalters: Thật vậy. Nhưng lưu ý rằng cách khác vẫn bị do cắt bớt. Giả thuyết của tôi là dân gian đầy đặn cho phần sau vì những cạm bẫy thống kê khó hiểu hơn!
Bathsheba

13

Đoạn mã ví dụ này minh họa rằng đó std::randlà một trường hợp của balderdash sùng bái hàng hóa cũ sẽ khiến bạn nhướng mày mỗi khi nhìn thấy nó.

Có một số vấn đề ở đây:

Hợp đồng mà mọi người thường cho rằng — ngay cả những linh hồn nghèo khó, những người không biết gì tốt hơn và sẽ không nghĩ ra nó một cách chính xác những điều khoản này — là randcác mẫu từ phân phối đồng đều trên các số nguyên trong 0, 1, 2,… RAND_MAX, và mỗi cuộc gọi mang lại một mẫu độc lập .

Vấn đề đầu tiên là hợp đồng giả định, các mẫu ngẫu nhiên đồng nhất độc lập trong mỗi cuộc gọi, không thực sự như những gì tài liệu nói — và trên thực tế, việc triển khai trong lịch sử đã không cung cấp ngay cả sự độc lập đơn giản nhất. Ví dụ, C99 §7.20.2.1 ' randHàm' nói mà không cần giải thích:

Các randchức năng tính toán một chuỗi các số nguyên giả ngẫu nhiên trong khoảng từ 0 đến RAND_MAX.

Đây là một câu vô nghĩa, bởi vì tính ngẫu nhiên giả là thuộc tính của một hàm (hoặc họ hàm ), không phải của một số nguyên, nhưng điều đó không ngăn được ngay cả các quan chức ISO lạm dụng ngôn ngữ này. Rốt cuộc, những độc giả duy nhất sẽ khó chịu vì nó biết tốt hơn là đọc tài liệu randvì sợ tế bào não của họ bị phân hủy.

Một triển khai lịch sử điển hình trong C hoạt động như thế này:

static unsigned int seed = 1;

static void
srand(unsigned int s)
{
    seed = s;
}

static unsigned int
rand(void)
{
    seed = (seed*1103515245 + 12345) % ((unsigned long)RAND_MAX + 1);
    return (int)seed;
}

Điều này có đặc tính đáng tiếc là mặc dù một mẫu đơn lẻ có thể được phân phối đồng đều dưới một hạt ngẫu nhiên đồng nhất (phụ thuộc vào giá trị cụ thể của RAND_MAX), nó sẽ luân phiên giữa các số nguyên chẵn và lẻ trong các lần gọi liên tiếp — sau

int a = rand();
int b = rand();

biểu thức (a & 1) ^ (b & 1)cho kết quả 1 với xác suất 100%, điều này không xảy ra đối với các mẫu ngẫu nhiên độc lập trên bất kỳ phân phối nào được hỗ trợ trên số nguyên chẵn và lẻ. Do đó, một giáo phái về hàng hóa xuất hiện rằng người ta nên loại bỏ các bit bậc thấp để đuổi theo con thú khó nắm bắt về 'tính ngẫu nhiên tốt hơn'. (Cảnh báo spoiler: Đây không phải là một thuật ngữ chuyên môn. Đây là một dấu hiệu cho thấy bất kỳ ai mà bạn đang đọc văn xuôi đều không biết họ đang nói về điều gì hoặc nghĩ rằng bạn không biết gì và cần phải hạ mình.)

Vấn đề thứ hai là ngay cả khi mỗi lệnh gọi lấy mẫu độc lập với phân phối ngẫu nhiên đồng nhất trên 0, 1, 2,…, RAND_MAXthì kết quả của rand() % 6sẽ không được phân phối đồng nhất trong 0, 1, 2, 3, 4, 5 giống như một con súc sắc cuộn, trừ khi RAND_MAXđồng dư với -1 modulo 6. Ví dụ đếm đơn giản: Nếu RAND_MAX= 6, thì từ rand(), tất cả các kết quả có xác suất bằng nhau 1/7, nhưng từ rand() % 6, kết quả 0 có xác suất 2/7 trong khi tất cả các kết quả khác có xác suất 1/7 .

Cách thích hợp để làm điều này là lấy mẫu từ chối: liên tục lấy mẫu ngẫu nhiên đồng nhất độc lập stừ 0, 1, 2,… RAND_MAX, và loại bỏ (ví dụ) các kết quả 0, 1, 2,…, ((RAND_MAX + 1) % 6) - 1—nếu bạn nhận được một trong các những, bắt đầu lại; nếu không, hãy nhường s % 6.

unsigned int s;
while ((s = rand()) < ((unsigned long)RAND_MAX + 1) % 6)
    continue;
return s % 6;

Bằng cách này, tập hợp các kết quả rand()mà chúng ta chấp nhận sẽ chia đều cho 6 và mỗi kết quả có thể xảy ra từ đó s % 6nhận được bởi cùng một số kết quả được chấp nhận từ đó rand(), vì vậy nếu rand()được phân phối đồng đều thì cũng vậy s. Không có ràng buộc về số lần thử nghiệm, nhưng số lượng dự kiến nhỏ hơn 2 và xác suất thành công tăng theo cấp số nhân với số lần thử nghiệm.

Việc lựa chọn kết quả nào màrand() bạn từ chối là không quan trọng, miễn là bạn ánh xạ một số lượng bằng nhau với mỗi số nguyên dưới 6. Mã tại cppreference.com đưa ra một lựa chọn khác , vì vấn đề đầu tiên ở trên — rằng không có gì được đảm bảo về phân phối hoặc sự độc lập của các đầu ra rand()và trên thực tế, các bit bậc thấp thể hiện các mẫu không 'trông đủ ngẫu nhiên' (đừng nhớ rằng đầu ra tiếp theo là một hàm xác định của đầu ra trước đó).

Bài tập dành cho người đọc: Chứng minh rằng mã tại cppreference.com mang lại phân bố đồng đều trên các cuộn khuôn nếu rand()mang lại phân phối đồng đều trên 0, 1, 2,… , RAND_MAX.

Bài tập cho người đọc: Tại sao bạn có thể thích một hoặc các tập hợp con khác từ chối? Tính toán nào là cần thiết cho mỗi thử nghiệm trong hai trường hợp?

Một vấn đề thứ ba là không gian hạt giống quá nhỏ nên ngay cả khi hạt giống được phân phối đồng đều, một kẻ thù được trang bị kiến ​​thức về chương trình của bạn và một kết quả nhưng không hạt giống có thể dễ dàng dự đoán hạt giống và các kết quả tiếp theo, điều này khiến chúng có vẻ không như vậy ngẫu nhiên sau cùng. Vì vậy, đừng nghĩ đến việc sử dụng điều này cho mật mã.

Bạn có thể đi theo con đường ưa thích và std::uniform_int_distributionlớp học của C ++ 11 với một thiết bị ngẫu nhiên thích hợp và công cụ ngẫu nhiên yêu thích của bạn như twister Mersenne phổ biến từng được yêu thích std::mt19937để chơi xúc xắc với người em họ bốn tuổi của bạn, nhưng ngay cả điều đó sẽ không có phù hợp để tạo ra mật mã chủ chốt vật liệu và Mersenne twister là một không gian con heo khủng khiếp quá với một đa kilobyte tình trạng tàn phá trên bộ nhớ cache của CPU của bạn với một thời gian thiết lập khiêu dâm, vì vậy nó là xấu ngay cả đối với, ví dụ như , song song mô phỏng Monte Carlo với cây tái tạo của các máy tính con; sự phổ biến của nó có lẽ chủ yếu xuất phát từ cái tên hấp dẫn của nó. Nhưng bạn có thể sử dụng nó để lăn xúc xắc đồ chơi như ví dụ này!

Một cách tiếp cận khác là sử dụng trình tạo số giả ngẫu nhiên mật mã đơn giản với trạng thái nhỏ, chẳng hạn như PRNG xóa khóa nhanh đơn giản hoặc chỉ một mật mã dòng như AES-CTR hoặc ChaCha20 nếu bạn tự tin ( ví dụ: trong mô phỏng Monte Carlo cho nghiên cứu trong khoa học tự nhiên) rằng không có hậu quả bất lợi nào đối với việc dự đoán kết quả trong quá khứ nếu trạng thái bị tổn hại.


4
"thời gian thiết lập tục tĩu" Dù sao thì bạn cũng không nên sử dụng nhiều hơn một trình tạo số ngẫu nhiên (mỗi luồng), vì vậy thời gian thiết lập sẽ bị khấu hao trừ khi chương trình của bạn chạy không lâu.
JAB

2
Phản đối BTW vì không hiểu rằng vòng lặp trong câu hỏi đang thực hiện lấy mẫu từ chối giống hệt nhau, có cùng (RAND_MAX + 1 )% 6giá trị. Việc bạn chia nhỏ các kết quả có thể xảy ra như thế nào không quan trọng . Bạn có thể từ chối chúng từ bất kỳ đâu trong phạm vi [0, RAND_MAX), miễn là kích thước của phạm vi được chấp nhận là bội số của 6. Địa ngục, bạn có thể từ chối bất kỳ kết quả nào x>6và bạn sẽ không cần %6nữa.
MSalters

12
Tôi không hoàn toàn hài lòng với câu trả lời này. Những người theo dõi có thể tốt nhưng bạn đang đưa nó đi sai hướng. Ví dụ, bạn phàn nàn rằng “tính ngẫu nhiên tốt hơn” không phải là một thuật ngữ chuyên môn và nó vô nghĩa. Điều này đúng một nửa. Vâng, nó không phải là một thuật ngữ chuyên môn, nhưng nó là một cách viết tắt hoàn toàn có ý nghĩa trong ngữ cảnh. Để nói bóng gió rằng những người sử dụng một thuật ngữ như vậy hoặc là thiếu hiểu biết hoặc độc hại, bản thân nó là một trong những điều này. “Độ ngẫu nhiên tốt” có thể rất khó xác định chính xác nhưng đủ dễ dàng để nắm bắt khi một hàm tạo ra kết quả với các thuộc tính ngẫu nhiên tốt hơn hoặc kém hơn.
Konrad Rudolph

3
Tôi thích câu trả lời này. Nó hơi thiếu thông tin, nhưng nó có rất nhiều thông tin cơ bản tốt. Hãy nhớ rằng, các chuyên gia THỰC SỰ chỉ sử dụng máy phát ngẫu nhiên phần cứng, vấn đề là khó như vậy.
Tiger4Hire

10
Đối với tôi thì ngược lại. Mặc dù nó chứa thông tin tốt, nhưng nó quá khó để xem như bất cứ điều gì ngoài ý kiến. Tính hữu dụng sang một bên.
Mr Lister

2

Tôi không phải là một người dùng C ++ có kinh nghiệm, nhưng quan tâm đến việc xem liệu các câu trả lời khác có liên quan đến std::rand()/((RAND_MAX + 1u)/6)việc ít thành kiến ​​hơn 1+std::rand()%6thực sự đúng hay không. Vì vậy, tôi đã viết một chương trình thử nghiệm để lập bảng kết quả cho cả hai phương pháp (Tôi chưa viết C ++ từ lâu, vui lòng kiểm tra nó). Một liên kết để chạy mã được tìm thấy ở đây . Nó cũng được tái tạo như sau:

// Example program
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <string>

int main()
{
    std::srand(std::time(nullptr)); // use current time as seed for random generator

    // Roll the die 6000000 times using the supposedly unbiased method and keep track of the results

    int results[6] = {0,0,0,0,0,0};

    // roll a 6-sided die 20 times
    for (int n=0; n != 6000000; ++n) {
        int x = 7;
        while(x > 6) 
            x = 1 + std::rand()/((RAND_MAX + 1u)/6);  // Note: 1+rand()%6 is biased

        results[x-1]++;
    }

    for (int n=0; n !=6; n++) {
        std::cout << results[n] << ' ';
    }

    std::cout << "\n";


    // Roll the die 6000000 times using the supposedly biased method and keep track of the results

    int results_bias[6] = {0,0,0,0,0,0};

    // roll a 6-sided die 20 times
    for (int n=0; n != 6000000; ++n) {
        int x = 7;
        while(x > 6) 
            x = 1 + std::rand()%6;

        results_bias[x-1]++;
    }

    for (int n=0; n !=6; n++) {
        std::cout << results_bias[n] << ' ';
    }
}

Sau đó, tôi lấy đầu ra của điều này và sử dụng chisq.testhàm trong R để chạy kiểm tra Chi-square để xem liệu kết quả có khác biệt đáng kể so với mong đợi hay không. Câu hỏi stackexchange này đi vào chi tiết hơn về việc sử dụng phép thử chi-square để kiểm tra tính công bằng của khuôn: Làm cách nào để tôi có thể kiểm tra xem một khuôn có công bằng hay không? . Đây là kết quả cho một vài lần chạy:

> ?chisq.test
> unbias <- c(100150, 99658, 100319, 99342, 100418, 100113)
> bias <- c(100049, 100040, 100091, 99966, 100188, 99666 )

> chisq.test(unbias)

Chi-squared test for given probabilities

data:  unbias
X-squared = 8.6168, df = 5, p-value = 0.1254

> chisq.test(bias)

Chi-squared test for given probabilities

data:  bias
X-squared = 1.6034, df = 5, p-value = 0.9008

> unbias <- c(998630, 1001188, 998932, 1001048, 1000968, 999234 )
> bias <- c(1000071, 1000910, 999078, 1000080, 998786, 1001075   )
> chisq.test(unbias)

Chi-squared test for given probabilities

data:  unbias
X-squared = 7.051, df = 5, p-value = 0.2169

> chisq.test(bias)

Chi-squared test for given probabilities

data:  bias
X-squared = 4.319, df = 5, p-value = 0.5045

> unbias <- c(998630, 999010, 1000736, 999142, 1000631, 1001851)
> bias <- c(999803, 998651, 1000639, 1000735, 1000064,1000108)
> chisq.test(unbias)

Chi-squared test for given probabilities

data:  unbias
X-squared = 7.9592, df = 5, p-value = 0.1585

> chisq.test(bias)

Chi-squared test for given probabilities

data:  bias
X-squared = 2.8229, df = 5, p-value = 0.7273

Trong ba lần chạy mà tôi đã thực hiện, giá trị p cho cả hai phương pháp luôn lớn hơn giá trị alpha điển hình được sử dụng để kiểm tra mức ý nghĩa (0,05). Điều này có nghĩa là chúng tôi sẽ không coi ai trong số họ là thiên vị. Điều thú vị là, phương pháp được cho là không thiên vị luôn có giá trị p thấp hơn, điều này cho thấy rằng nó thực sự có thể thiên vị hơn. Báo trước rằng tôi chỉ thực hiện 3 lần chạy.

CẬP NHẬT: Trong khi tôi viết câu trả lời của mình, Konrad Rudolph đã đăng một câu trả lời có cùng cách tiếp cận, nhưng nhận được một kết quả rất khác. Tôi không có danh tiếng để bình luận về câu trả lời của anh ấy, vì vậy tôi sẽ giải quyết nó ở đây. Đầu tiên, điều chính là mã mà anh ta sử dụng sử dụng cùng một hạt giống cho trình tạo số ngẫu nhiên mỗi khi nó chạy. Nếu bạn thay đổi hạt giống, bạn thực sự nhận được nhiều kết quả. Thứ hai, nếu bạn không thay đổi hạt giống, nhưng thay đổi số lần thử nghiệm, bạn cũng nhận được nhiều kết quả khác nhau. Hãy thử tăng hoặc giảm theo thứ tự độ lớn để xem tôi muốn nói gì. Thứ ba, có một số việc cắt ngắn hoặc làm tròn số nguyên đang diễn ra trong đó các giá trị mong đợi không hoàn toàn chính xác. Nó có lẽ không đủ để tạo ra sự khác biệt, nhưng nó ở đó.

Nói tóm lại, về cơ bản, anh ta chỉ tình cờ mua đúng hạt giống và số lần thử nghiệm mà anh ta có thể nhận được kết quả sai.


Việc triển khai của bạn có một lỗ hổng nghiêm trọng do sự hiểu lầm từ phía bạn: đoạn trích dẫn không được so sánh rand()%6với rand()/(1+RAND_MAX)/6. Đúng hơn, nó là so sánh việc lấy mẫu đơn giản còn lại với lấy mẫu từ chối (xem các câu trả lời khác để giải thích). Do đó, mã thứ hai của bạn bị sai ( whilevòng lặp không làm gì cả). Kiểm tra thống kê của bạn cũng có vấn đề (bạn không thể chỉ chạy lặp lại kiểm tra của mình để tìm độ chắc chắn, bạn đã không thực hiện hiệu chỉnh,…).
Konrad Rudolph

1
@KonradRudolph Tôi không có đại diện để nhận xét về câu trả lời của bạn, vì vậy tôi đã thêm nó như một bản cập nhật cho câu trả lời của mình. Của bạn cũng có một lỗ hổng nghiêm trọng là nó sẽ xảy ra sử dụng một tập hợp hạt giống và số lần thử nghiệm mỗi lần chạy dẫn đến kết quả sai. Nếu bạn đã chạy lặp lại với các hạt giống khác nhau, bạn có thể đã nắm bắt được điều đó. Nhưng có, bạn đang sửa trong khi vòng lặp không có gì, nhưng nó cũng không làm thay đổi kết quả mà khối mã đặc biệt
anjama

Tôi đã chạy lặp lại, thực sự. Hạt giống cố ý không được thiết lập vì việc đặt một hạt giống ngẫu nhiên với std::srand(và không sử dụng <random>) là điều khá khó thực hiện theo cách tuân thủ các tiêu chuẩn và tôi không muốn sự phức tạp của nó làm giảm đi mã còn lại. Nó cũng không liên quan đến tính toán: lặp lại cùng một trình tự trong một mô phỏng là hoàn toàn có thể chấp nhận được. Tất nhiên các hạt giống khác nhau sẽ cho kết quả khác nhau, và một số sẽ không đáng kể. Điều đó hoàn toàn được mong đợi dựa trên cách xác định giá trị p.
Konrad Rudolph

1
Chuột, tôi đã mắc lỗi trong lần lặp lại của mình; và bạn nói đúng, số lượng tử thứ 95 của lần chạy lặp lại khá gần với p = 0,05 - tức là chính xác những gì chúng ta mong đợi khi đó là null. Tóm lại, việc triển khai thư viện tiêu chuẩn của tôi về việc std::randtạo ra các mô phỏng tung đồng xu rất tốt cho d6, trên phạm vi hạt ngẫu nhiên.
Konrad Rudolph

1
Ý nghĩa thống kê chỉ là một phần của câu chuyện. Bạn có một giả thuyết rỗng (được phân phối đồng đều) và một giả thuyết thay thế (sai lệch mô-đun) — về thực tế, một nhóm các giả thuyết thay thế, được lập chỉ mục theo sự lựa chọn RAND_MAX, xác định kích thước ảnh hưởng của sai lệch mô-đun. Ý nghĩa thống kê là xác suất theo giả thuyết không mà bạn bác bỏ nó một cách sai lầm. Là gì sức mạnh thống kê xác suất theo một giả thuyết khác rằng thử nghiệm của bạn - một cách chính xác bác bỏ giả thuyết không? Bạn có phát hiện rand() % 6theo cách này khi RAND_MAX = 2 ^ 31 - 1?
Squeamish Ossifrage

2

Người ta có thể nghĩ về một trình tạo số ngẫu nhiên như đang làm việc trên một dòng các chữ số nhị phân. Bộ tạo chuyển luồng thành số bằng cách cắt nó thành nhiều phần. Nếu std:randhàm đang hoạt động với RAND_MAX32767, thì nó đang sử dụng 15 bit trong mỗi lát cắt.

Khi người ta lấy các mô-đun của một số từ 0 đến 32767, người ta thấy rằng 5462 '0's và' 1's nhưng chỉ 5461 '2's,' 3's, '4's và' 5's. Do đó kết quả là sai lệch. Giá trị RAND_MAX càng lớn thì càng có ít sai lệch, nhưng không thể tránh khỏi.

Những gì không thiên vị là một số trong phạm vi [0 .. (2 ^ n) -1]. Bạn có thể tạo một số tốt hơn (về mặt lý thuyết) trong phạm vi 0..5 bằng cách trích xuất 3 bit, chuyển đổi chúng thành số nguyên trong phạm vi 0..7 và từ chối 6 và 7.

Người ta hy vọng rằng mọi bit trong luồng bit đều có cơ hội ngang nhau là '0' hoặc '1' bất kể nó nằm ở đâu trong luồng hoặc giá trị của các bit khác. Điều này đặc biệt khó trong thực tế. Nhiều cách triển khai khác nhau của PRNG phần mềm đưa ra những thỏa hiệp khác nhau giữa tốc độ và chất lượng. Một máy phát đồng dư tuyến tính chẳng hạn như std::randcung cấp tốc độ nhanh nhất cho chất lượng thấp nhất. Trình tạo mật mã cung cấp chất lượng cao nhất cho tốc độ thấp nhất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.