Tại sao rand () lặp lại số thường xuyên hơn trên Linux so với Mac?


87

Tôi đã triển khai một hashmap trong C như một phần của dự án tôi đang thực hiện và sử dụng các phần chèn ngẫu nhiên để kiểm tra nó khi tôi nhận thấy rằng rand()trên Linux dường như lặp lại các con số thường xuyên hơn nhiều so với trên Mac. RAND_MAXlà 2147483647 / 0x7FFFFFFF trên cả hai nền tảng. Tôi đã giảm nó xuống chương trình thử nghiệm này tạo ra một mảng byte RAND_MAX+1dài, tạo ra RAND_MAXcác số ngẫu nhiên, ghi chú nếu mỗi cái là một bản sao và kiểm tra nó ra khỏi danh sách như đã thấy.

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

int main() {
    size_t size = ((size_t)RAND_MAX) + 1;
    char *randoms = calloc(size, sizeof(char));
    int dups = 0;
    srand(time(0));
    for (int i = 0; i < RAND_MAX; i++) {
        int r = rand();
        if (randoms[r]) {
            // printf("duplicate at %d\n", r);
            dups++;
        }
        randoms[r] = 1;
    }
    printf("duplicates: %d\n", dups);
}

Linux liên tục tạo ra khoảng 790 triệu bản sao. Mac luôn chỉ tạo một, do đó, nó lặp qua mọi số ngẫu nhiên mà nó có thể tạo gần như không lặp lại. Bất cứ ai có thể xin vui lòng giải thích cho tôi làm thế nào điều này làm việc? Tôi không thể nói bất cứ điều gì khác với các trang dành cho nam giới, không thể biết RNG mỗi trang đang sử dụng và không thể tìm thấy bất cứ điều gì trực tuyến. Cảm ơn!


4
Vì rand () trả về các giá trị từ 0..RAND_MAX, nên mảng của bạn cần có kích thước RAND_MAX + 1
Blastfurnace

21
Bạn có thể nhận thấy rằng RAND_MAX / e ~ = 790 triệu. Ngoài ra giới hạn của (1-1 / n) ^ n khi n tiến đến vô cùng là 1 / e.
David Schwartz

3
@DavidSchwartz Nếu tôi hiểu bạn một cách chính xác, điều đó có thể giải thích tại sao con số trên Linux luôn ở mức khoảng 790 triệu. Tôi đoán câu hỏi sau đó là: tại sao / làm thế nào mà Mac không lặp lại điều đó nhiều lần?
Theron S

26
Không có yêu cầu chất lượng cho PRNG trong thư viện thời gian chạy. Chỉ có yêu cầu thực sự là lặp lại với cùng một hạt giống. Rõ ràng, chất lượng của PRNG trong linux của bạn tốt hơn so với Mac của bạn.
PMG

4
@chux Có, nhưng vì dựa trên phép nhân, trạng thái không bao giờ có thể bằng 0 hoặc kết quả (trạng thái tiếp theo) cũng sẽ bằng không. Dựa trên mã nguồn, nó sẽ kiểm tra số 0 như một trường hợp đặc biệt nếu được tạo thành số 0, nhưng nó không bao giờ tạo ra số 0 như một phần của chuỗi.
Arkku

Câu trả lời:


119

Mặc dù lúc đầu nghe có vẻ như macOS rand()tốt hơn vì không lặp lại bất kỳ số nào, nhưng bạn nên lưu ý rằng với số lượng được tạo ra này, dự kiến ​​sẽ có nhiều bản sao (thực tế là khoảng 790 triệu hoặc (2 31 -1 -1 ) / e ). Tương tự lặp đi lặp lại qua các số theo thứ tự cũng sẽ không tạo ra sự trùng lặp, nhưng sẽ không được coi là rất ngẫu nhiên. Vì vậy, việc rand()triển khai Linux trong thử nghiệm này không thể phân biệt được với một nguồn ngẫu nhiên thực sự, trong khi macOS rand()thì không.

Một điều đáng ngạc nhiên khác thoạt nhìn là cách macOS rand() có thể quản lý để tránh trùng lặp rất tốt. Nhìn vào mã nguồn của nó , chúng tôi thấy việc thực hiện như sau:

/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
    long hi, lo, x;

    /* Can't be initialized with 0, so use another value. */
    if (*ctx == 0)
        *ctx = 123459876;
    hi = *ctx / 127773;
    lo = *ctx % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    return ((*ctx = x) % ((unsigned long) RAND_MAX + 1));

Điều này thực sự dẫn đến tất cả các số từ 1 đến RAND_MAX, bao gồm, chính xác một lần, trước khi chuỗi lặp lại. Vì trạng thái tiếp theo dựa trên phép nhân, trạng thái không bao giờ có thể bằng 0 (hoặc tất cả các trạng thái trong tương lai cũng sẽ bằng không). Do đó, số lặp lại mà bạn thấy là số đầu tiên và số 0 là số không bao giờ được trả về.

Apple đã thúc đẩy việc sử dụng các trình tạo số ngẫu nhiên tốt hơn trong tài liệu và ví dụ của họ ít nhất là miễn là macOS (hoặc OS X) đã tồn tại, do đó, chất lượng của rand() có lẽ không được coi là quan trọng và họ chỉ bị mắc kẹt với một trong những các trình tạo giả ngẫu nhiên đơn giản nhất hiện có. (Như bạn đã lưu ý, họ rand()thậm chí còn được nhận xét với một đề xuất sử dụng arc4random()thay thế.)

Trên một lưu ý liên quan, trình tạo số giả ngẫu nhiên đơn giản nhất mà tôi có thể tìm thấy tạo ra kết quả tốt trong thử nghiệm này (và nhiều thử nghiệm khác) cho tính ngẫu nhiên là xorshift * :

uint64_t x = *ctx;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*ctx = x;
return (x * 0x2545F4914F6CDD1DUL) >> 33;

Việc thực hiện này cho kết quả gần như chính xác là 790 triệu bản sao trong bài kiểm tra của bạn.


5
Một bài báo được xuất bản vào những năm 1980 đã đề xuất một bài kiểm tra thống kê cho PRNG dựa trên "vấn đề sinh nhật".
pjs

14
"Apple đã thúc đẩy việc sử dụng các trình tạo số ngẫu nhiên tốt hơn trong tài liệu của họ" -> tất nhiên Apple có thể sử dụng arc4random()mã như phía sau rand()và nhận được rand()kết quả tốt . Thay vì cố gắng điều khiển các lập trình viên viết mã khác nhau, chỉ cần tạo các chức năng thư viện tốt hơn. "họ vừa bị mắc kẹt" là lựa chọn của họ.
chux - Phục hồi

23
việc thiếu phần bù không đổi trong mac rand()làm cho nó tệ đến mức không hữu ích cho việc sử dụng thực tế: Tại sao rand ()% 7 luôn trả về 0? , Rand ()% 14 chỉ tạo ra các giá trị 6 hoặc 13
phuclv

4
@PeterCordes: Có một yêu cầu như vậy rand, đó là chạy lại nó với cùng một hạt giống tạo ra cùng một chuỗi. OpenBSD randbị hỏng và không tuân theo hợp đồng này.
R .. GitHub DỪNG GIÚP ICE

8
@ R..GitHubSTOPHELPINGICE Bạn có thấy một yêu cầu C mà rand()với cùng một hạt giống tạo ra trình tự giống nhau giữa các phiên bản khác nhau của thư viện không? Bảo đảm như vậy có thể hữu ích cho kiểm tra hồi quy giữa các phiên bản thư viện, nhưng tôi không tìm thấy yêu cầu C nào cho nó.
chux - Phục hồi lại

34

MacOS cung cấp hàm rand () không có giấy tờ trong stdlib. Nếu bạn không thấy nó, thì các giá trị đầu tiên mà nó đưa ra là 16807, 282475249, 1622650073, 984943658 và 1144108930. Một tìm kiếm nhanh sẽ cho thấy chuỗi này tương ứng với trình tạo số ngẫu nhiên LCG rất cơ bản lặp lại công thức sau:

x n +1 = 7 5 · x n (mod 2 31 - 1)

Do trạng thái của RNG này được mô tả hoàn toàn bằng giá trị của một số nguyên 32 bit duy nhất, nên chu kỳ của nó không dài lắm. Nói chính xác, nó lặp lại cứ sau 2 31 - 2 lần lặp, xuất ra mọi giá trị từ 1 đến 2 31 - 2.

Tôi không nghĩ rằng có một triển khai rand () tiêu chuẩn cho tất cả các phiên bản Linux, nhưng có một glibc rand () thường được sử dụng. Thay vì một biến trạng thái 32 bit duy nhất, điều này sử dụng một nhóm hơn 1000 bit, mà tất cả các ý định và mục đích sẽ không bao giờ tạo ra một chuỗi lặp lại hoàn toàn. Một lần nữa, bạn có thể tìm ra phiên bản nào bạn có bằng cách in một vài kết quả đầu ra từ RNG này mà không cần gieo nó trước. (Hàm glibc rand () tạo ra các số 1804289383, 846930886, 1681692777, 1714636915 và 1957747793.)

Vì vậy, lý do bạn nhận được nhiều xung đột hơn trong Linux (và hầu như không có trong MacOS) là vì phiên bản Linux của rand () về cơ bản là ngẫu nhiên hơn.


5
một người vô danh rand()phải cư xử như một người vớisrand(1);
pmg

5
Các mã nguồn cho rand()trong hệ điều hành MacOS có sẵn: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/... FWIW, tôi chạy cùng một thử nghiệm chống lại điều này tổng hợp từ các nguồn và nó thực sự gây ra chỉ có một bản sao. Apple đã thúc đẩy việc sử dụng các trình tạo số ngẫu nhiên khác (như arc4random()trước khi Swift tiếp quản) trong các ví dụ và tài liệu của họ, vì vậy việc sử dụng rand()có lẽ không phổ biến trong các ứng dụng gốc trên nền tảng của họ, điều này có thể giải thích tại sao nó không tốt hơn.
Arkku

Cảm ơn đã trả lời, mà trả lời câu hỏi của tôi. Và một khoảng thời gian (2 ^ 31) -2 giải thích lý do tại sao nó sẽ bắt đầu lặp lại ngay khi kết thúc như tôi đã quan sát. Bạn (@ r3mainer) cho biết rand()không có giấy tờ, nhưng @Arkku đã cung cấp một liên kết đến nguồn rõ ràng. Bạn có biết tại sao tôi không thể tìm thấy tệp đó trên hệ thống của mình không và tại sao tôi chỉ thấy int rand(void) __swift_unavailable("Use arc4random instead.");trong máy Mac stdlib.h? Tôi cho rằng mã @Arkku được liên kết đến chỉ được biên dịch vào ... thư viện gì?
Theron S

1
@TheronS Nó được biên dịch vào thư viện C, libc /usr/lib/libc.dylib,. =)
Arkku

5
Phiên bản nào của rand()một điều kiện sử dụng chương trình C không được xác định bởi "biên dịch" hay "hệ điều hành", nhưng thay vì thực hiện các thư viện chuẩn C (ví dụ glibc, libc.dylib, msvcrt*.dll).
Peter O.

10

rand()được xác định bởi tiêu chuẩn C và tiêu chuẩn C không chỉ định sử dụng thuật toán nào. Rõ ràng, Apple đang sử dụng thuật toán kém hơn so với triển khai GNU / Linux của bạn: Linux không thể phân biệt được với một nguồn ngẫu nhiên thực sự trong thử nghiệm của bạn, trong khi việc triển khai của Apple chỉ xáo trộn các con số xung quanh.

Nếu bạn muốn số ngẫu nhiên có chất lượng bất kỳ, hãy sử dụng PRNG tốt hơn mang lại ít nhất một số đảm bảo về chất lượng của số mà nó trả về hoặc chỉ cần đọc từ /dev/urandomhoặc tương tự. Càng về sau cung cấp cho bạn số chất lượng mật mã, nhưng chậm. Ngay cả khi nó quá chậm, /dev/urandomcó thể cung cấp một số hạt giống tuyệt vời cho một số PRNG khác, nhanh hơn.


Cảm ơn vi đa trả lơi. Tôi thực sự không cần một PRNG tốt, chỉ lo ngại rằng có một số hành vi không xác định ẩn trong hashmap của tôi, sau đó tò mò khi tôi loại bỏ khả năng đó và các nền tảng vẫn hoạt động khác đi.
Theron S

btw đây là một ví dụ về trình tạo số ngẫu nhiên an toàn bằng mật mã: github.com/divinity76/phpcpp/commit/ mẹo - nhưng đó là C ++ thay vì C và tôi sẽ để những người triển khai STL thực hiện mọi công việc nặng nhọc ..
hanshenrik

3
@hanshenrik Một RNG tiền điện tử nói chung là quá mức cần thiết và quá chậm cho một bảng băm đơn giản.
PM 2Ring

1
@ PM2Ring Hoàn toàn. Một bảng băm băm chủ yếu cần phải nhanh, không tốt. Tuy nhiên, nếu bạn muốn phát triển một thuật toán bảng băm không chỉ nhanh mà còn tốt, tôi tin rằng có ích khi biết một số thủ thuật của thuật toán băm mật mã. Nó sẽ giúp bạn tránh được hầu hết các lỗi sai rõ ràng nhất trong các thuật toán băm nhanh nhất. Tuy nhiên, tôi sẽ không quảng cáo cho việc thực hiện cụ thể ở đây.
cmaster - phục hồi monica

@cmaster Đúng vậy. Đó chắc chắn là một ý tưởng tốt để biết một chút về những thứ như trộn các chức nănghiệu ứng tuyết lở . May mắn thay, có các hàm băm không mã hóa với các thuộc tính tốt không hy sinh quá nhiều tốc độ (khi được triển khai chính xác), ví dụ: xxhash, murmur3 hoặc siphash.
PM 2Ring

5

Nói chung, cặp rand / srand đã bị coi là loại không dùng được trong một thời gian dài do các bit thứ tự thấp hiển thị ít ngẫu nhiên hơn các bit thứ tự cao trong kết quả. Điều này có thể có hoặc không liên quan gì đến kết quả của bạn, nhưng tôi nghĩ đây vẫn là một cơ hội tốt để nhớ rằng mặc dù một số triển khai rand / srand hiện đã được cập nhật hơn, các triển khai cũ vẫn tồn tại và tốt hơn là sử dụng ngẫu nhiên (3 ). Trên hộp Arch Linux của tôi, ghi chú sau vẫn còn trong trang man cho rand (3):

  The versions of rand() and srand() in the Linux C Library use the  same
   random number generator as random(3) and srandom(3), so the lower-order
   bits should be as random as the higher-order bits.  However,  on  older
   rand()  implementations,  and  on  current implementations on different
   systems, the lower-order bits are much less random than the  higher-or-
   der bits.  Do not use this function in applications intended to be por-
   table when good randomness is needed.  (Use random(3) instead.)

Ngay bên dưới đó, trang man thực sự đưa ra các ví dụ rất ngắn, rất đơn giản về rand và srand nói về các LC RNG đơn giản nhất bạn từng thấy và có RAND_MAX nhỏ. Tôi không nghĩ rằng họ phù hợp với những gì trong thư viện tiêu chuẩn C, nếu họ đã từng làm. Hoặc ít nhất tôi hy vọng là không.

Nói chung, nếu bạn sẽ sử dụng một cái gì đó từ thư viện tiêu chuẩn, hãy sử dụng ngẫu nhiên nếu bạn có thể (trang man liệt kê nó là tiêu chuẩn POSIX trở lại POSIX.1-2001, nhưng rand là cách tiêu chuẩn trở lại trước khi C thậm chí được chuẩn hóa) . Hoặc tốt hơn nữa, hãy mở công thức Numerical mở (hoặc tìm nó trực tuyến) hoặc Knuth và thực hiện nó. Chúng thực sự dễ dàng và bạn chỉ thực sự cần thực hiện một lần để có RNG cho mục đích chung với các thuộc tính bạn thường cần nhất và có chất lượng được biết đến.


Cảm ơn vì bối cảnh. Tôi thực sự không cần sự ngẫu nhiên chất lượng cao và đã triển khai MT19937, mặc dù ở Rust. Hầu như chỉ tò mò về cách tìm hiểu tại sao hai nền tảng hoạt động khác nhau.
Theron S

1
Đôi khi những câu hỏi hay nhất được hỏi vì sự quan tâm đơn giản thay vì nhu cầu khắt khe - có vẻ như đó thường là những câu hỏi mà quên đi một bộ câu trả lời hay từ một điểm tò mò cụ thể. Bạn là một trong số họ. Đây là tất cả những người tò mò, tin tặc thực sự và nguyên bản.
Thomas Kammeyer

Thật buồn cười khi lời khuyên là "ngừng sử dụng rand ()" thay vì làm cho rand () tốt hơn. Không có gì trong tiêu chuẩn từng nói rằng nó phải là một máy phát cụ thể.
đường ống

2
@pipe Nếu làm cho rand()'tốt hơn' có nghĩa là làm cho nó chậm hơn (điều có thể sẽ xảy ra - các số ngẫu nhiên được bảo mật bằng mật mã mất rất nhiều nỗ lực), thì có lẽ tốt hơn là giữ cho nó nhanh hơn ngay cả khi có thể dự đoán được nhiều hơn. Trường hợp cụ thể: chúng tôi đã có một ứng dụng sản xuất mất nhiều thời gian để khởi động, chúng tôi bắt nguồn từ một RNG mà việc khởi tạo cần phải chờ để có đủ entropy được tạo ra. Hóa ra nó không cần phải bảo mật, vì vậy thay thế nó bằng RNG "tệ hơn" là một cải tiến lớn.
chơi
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.