<random> tạo ra cùng một số trong Linux, nhưng không tạo trong Windows


90

Đoạn mã dưới đây nhằm tạo ra một danh sách gồm năm số giả ngẫu nhiên trong khoảng thời gian [1.100]. Tôi gieo hạt default_random_enginevới time(0), trả về thời gian hệ thống trong thời gian unix . Khi tôi biên dịch và chạy chương trình này trên Windows 7 bằng Microsoft Visual Studio 2013, nó hoạt động như mong đợi (xem bên dưới). Tuy nhiên, khi tôi làm như vậy trong Arch Linux với trình biên dịch g ++, nó hoạt động rất lạ.

Trong Linux, 5 số sẽ được tạo mỗi lần. 4 số cuối cùng sẽ khác nhau trên mỗi lần thực hiện (như thường lệ), nhưng số đầu tiên sẽ giữ nguyên.

Ví dụ đầu ra từ 5 lần thực thi trên Windows và Linux:

      | Windows:       | Linux:        
---------------------------------------
Run 1 | 54,01,91,73,68 | 25,38,40,42,21
Run 2 | 46,24,16,93,82 | 25,78,66,80,81
Run 3 | 86,36,33,63,05 | 25,17,93,17,40
Run 4 | 75,79,66,23,84 | 25,70,95,01,54
Run 5 | 64,36,32,44,85 | 25,09,22,38,13

Thêm vào bí ẩn, số đầu tiên đó tăng dần theo định kỳ trên Linux. Sau khi có được các kết quả đầu ra ở trên, tôi đợi khoảng 30 phút và thử lại để thấy rằng số đầu tiên đã thay đổi và bây giờ luôn được tạo dưới dạng số 26. Nó đã tiếp tục tăng lên 1 theo chu kỳ và bây giờ là 32. Nó có vẻ tương ứng. với giá trị thay đổi của time(0).

Tại sao số đầu tiên hiếm khi thay đổi qua các lần chạy, và sau đó khi số đó tăng lên 1?

Mật mã. Nó in ra 5 số và thời gian hệ thống một cách gọn gàng:

#include <iostream>
#include <random>
#include <time.h>

using namespace std;

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    time_t system_time = time(0);    

    default_random_engine e(system_time);
    uniform_int_distribution<int> u(lower_bound, upper_bound);

    cout << '#' << '\t' << "system time" << endl
         << "-------------------" << endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);
        cout << secret << '\t' << system_time << endl;
    }   

    system("pause");
    return 0;
}

3
Là gì sizeof(time_t)vs sizeof(default_random_engine::result_type)?
Mark Ransom

3
Lưu ý rằng default_random_enginehoàn toàn khác nhau trên hai nền tảng đó.
TC

1
Nó vẫn có thể là BTW ngẫu nhiên.
Alec Teal

5
Có phải mọi lập trình viên đều trải qua giai đoạn mà họ nghĩ rằng thời gian là một hạt giống tạo số ngẫu nhiên tốt không?
OldFart

6
@OldFart Có, nó được gọi là học viện.
Casey

Câu trả lời:


141

Đây là những gì đang xảy ra:

  • default_random_enginetrong libstdc ++ (thư viện chuẩn của GCC) minstd_rand0, là một công cụ đồng dư tuyến tính đơn giản:

    typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
  • Cách công cụ này tạo ra các số ngẫu nhiên là x i + 1 = (16807x i + 0) mod 2147483647.

  • Do đó, nếu các hạt khác nhau 1, thì phần lớn thời gian, số được tạo ra đầu tiên sẽ khác 16807.

  • Phạm vi của máy phát điện này là [1, 2147483646]. Cách libstdc ++ uniform_int_distributionánh xạ nó tới một số nguyên trong phạm vi [1, 100] về cơ bản là như sau: tạo một số n. Nếu số không lớn hơn 2147483600, thì trả về (n - 1) / 21474836 + 1; nếu không, hãy thử lại với một số mới.

    Dễ dàng thấy rằng trong đại đa số các trường hợp, hai ns chỉ khác nhau 16807 sẽ mang lại cùng một số trong [1, 100] theo quy trình này. Trên thực tế, người ta sẽ mong đợi con số được tạo ra sẽ tăng lên một khoảng sau mỗi 21474836/16807 = 1278 giây hoặc 21,3 phút, điều này khá phù hợp với quan sát của bạn.

MSVC của default_random_enginemt19937, mà không có vấn đề này.


36
Tôi tự hỏi điều gì đã khiến các nhà phát triển thư viện tiêu chuẩn của GCC chọn một mặc định kinh khủng như vậy.
CodesInChaos

13
@CodesInChaos Tôi không biết nó có liên quan gì không nhưng chuỗi công cụ MacOS / iOS cũng sử dụng cùng một công cụ ngẫu nhiên khủng khiếp, khiến rand()% 7 luôn trả về 0
phuclv 23/09/15

7
@ LưuVĩnhPhúc Không sửa rand()cũng là điều dễ hiểu (đó là di sản vô vọng tào lao). Sử dụng PRNG cấp độ cho một cái gì đó mới là điều không thể chấp nhận được. Tôi thậm chí còn coi đây là một hành vi vi phạm tiêu chuẩn, vì tiêu chuẩn yêu cầu "cung cấp hành vi động cơ ít nhất có thể chấp nhận được để sử dụng tương đối bình thường, rẻ tiền và / hoặc nhẹ." mà triển khai này không cung cấp vì nó thất bại nghiêm trọng ngay cả đối với các trường hợp sử dụng tầm thường như rand % 7ví dụ của bạn .
CodesInChaos

2
@CodesInChaos Tại sao việc sửa lỗi không rand()được hiểu một cách chính xác? Có phải chỉ vì không ai có thể nghĩ làm điều đó?
user253751 24/09/15

2
@immibis API đã bị hỏng nên tốt hơn hết bạn nên sử dụng một bản thay thế độc lập để khắc phục tất cả các vấn đề. 1) Thay thế thuật toán sẽ là một thay đổi đột phá, vì vậy bạn có thể cần một công tắc tương thích cho các chương trình cũ hơn. 2) Hạt giống srandquá nhỏ để dễ dàng tạo ra các hạt giống duy nhất. 3) Nó trả về một số nguyên với giới hạn trên được xác định triển khai mà người gọi phải giảm xuống bằng cách nào đó thành một số trong phạm vi mong muốn, khi được thực hiện đúng cách sẽ tốn nhiều công sức hơn là viết thay thế bằng một API lành mạnh cho rand()4) Nó sử dụng trạng thái có thể thay đổi toàn cục
CodesInChaos

30

Việc std::default_random_enginethực hiện được xác định. Sử dụng std::mt19937hoặc std::mt19937_64thay thế.

Ngoài ra std::timevà các ctimehàm không chính xác lắm, hãy sử dụng các kiểu được xác định trong <chrono>tiêu đề để thay thế:

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();

    std::mt19937 e;
    e.seed(static_cast<unsigned int>(t)); //Seed engine with timed value.
    std::uniform_int_distribution<int> u(lower_bound, upper_bound);

    std::cout << '#' << '\t' << "system time" << std::endl
    << "-------------------" << std::endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);

        std::cout << secret << '\t' << t << std::endl;
    }   

    system("pause");
    return 0;
}

3
Bạn có muốn sử dụng thời gian chính xác hơn khi gieo một trình tạo biến giả ngẫu nhiên không? Có lẽ điều này là ngây thơ, nhưng có vẻ như sự thiếu chính xác gần như là mong muốn nếu nó đưa vào entropy. (Trừ khi bạn có nghĩa là nó ít chính xác và do đó dẫn đến vật chất ít hạt giống tiềm năng.)
Nat

15
Tôi chỉ đề xuất sử dụng std::random_devicethay vì current_time để gieo mầm trình tạo ngẫu nhiên của bạn. Vui lòng kiểm tra bất kỳ ví dụ cppreference nào về Random.
Aleksander Fular

5
Nếu bạn không muốn bất kỳ ai đoán hạt giống của bạn (và do đó tái tạo trình tự của bạn) thì độ chính xác kém hơn không giống với độ ngẫu nhiên hơn. Hãy đi đến cực điểm: Làm tròn hạt giống của bạn để ngày sau (hoặc năm?) -> đoán rất dễ. Sử dụng chính xác femto giây -> Rất nhiều đoán để làm ...
linac

2
@ChemicalEngineer Độ chi tiết của ctimelà 1 giây. Mức độ chi tiết của std::chronoviệc triển khai là do người dùng xác định, được mặc định là for std::high_resolution_clock(trong Visual Studio, đó là định dạng cho std::steady_clock), nano giây nhưng có thể chọn một phép đo nhỏ hơn nhiều, do đó, chính xác hơn nhiều.
Casey

2
@linac Nếu bạn muốn các thuộc tính mật mã, bạn sẽ sử dụng prng thích hợp (không phải một prng được sử dụng trong câu trả lời này). Và tất nhiên hạt giống dựa trên thời gian cũng nằm ngoài câu hỏi, bất kể độ chính xác được hứa hẹn.
Cthulhu

-2

Trong Linux, hàm ngẫu nhiên không phải là một hàm ngẫu nhiên theo nghĩa xác suất, mà là một bộ tạo số ngẫu nhiên giả. Nó được ướp muối với một loại hạt, và dựa trên hạt đó, các con số được tạo ra là giả ngẫu nhiên và phân bố đồng đều. Cách thức Linux có lợi thế là trong việc thiết kế các thí nghiệm nhất định sử dụng thông tin từ các quần thể, có thể đo lường việc lặp lại thí nghiệm với sự điều chỉnh thông tin đầu vào đã biết. Khi chương trình cuối cùng đã sẵn sàng để thử nghiệm trong đời thực, muối (hạt giống), có thể được tạo bằng cách yêu cầu người dùng di chuyển chuột, kết hợp chuyển động của chuột với một số lần nhấn phím và thêm vào một số micro giây kể từ đầu lần bật nguồn cuối cùng.

Hạt giống số ngẫu nhiên của Windows được lấy từ bộ sưu tập số chuột, bàn phím, mạng và thời gian trong ngày. Nó không thể lặp lại. Nhưng giá trị muối này có thể được đặt lại thành một hạt đã biết, nếu như đã đề cập ở trên, một hạt có liên quan đến việc thiết kế một thí nghiệm.

Ồ vâng, Linux có hai trình tạo số ngẫu nhiên. Một, mặc định là modulo 32bits và cái còn lại là modulo 64bits. Sự lựa chọn của bạn phụ thuộc vào nhu cầu về độ chính xác và lượng thời gian tính toán mà bạn muốn sử dụng để thử nghiệm hoặc sử dụng thực tế.


5
Tôi không chắc tại sao bạn lại nói về thuật toán tạo hạt giống. OP rõ ràng sử dụng thời gian hệ thống như một hạt giống. Ngoài ra, bạn có thể thêm một số tham chiếu tớicollection of mouse, keyboard, network and time of day numbers
ngôn ngữ mặc định,
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.