<random> генерує таке саме число в Linux, але не в Windows


90

Наведений нижче код призначений для створення списку з п’яти псевдовипадкових чисел в інтервалі [1100]. Я Насіння default_random_engineз time(0), яка повертає системний час в UNIX час . Коли я компілюю та запускаю цю програму в Windows 7 за допомогою Microsoft Visual Studio 2013, вона працює належним чином (див. Нижче). Однак, коли я це роблю в Arch Linux із компілятором g ++, це поводиться дивно.

У Linux щоразу буде генеруватися 5 чисел. Останні 4 числа будуть різними при кожному виконанні (як це часто буває), але перше число залишатиметься незмінним.

Приклад результату з 5 виконань у Windows та 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

Додаючи загадки, перше число періодично збільшується на одиницю в Linux. Отримавши вищезазначені результати, я зачекав близько 30 хвилин і знову спробував виявити, що 1-е число змінилося і тепер завжди генерується як 26. Він продовжує періодично збільшуватися на 1 і зараз становить 32. Здається, це відповідає зі змінним значенням time(0).

Чому перше число рідко змінюється між циклами, а потім, коли воно змінюється, збільшується на 1?

Код. Він акуратно роздруковує 5 чисел і системний час:

#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
Що таке sizeof(time_t)проти sizeof(default_random_engine::result_type)?
Марк Ренсом

3
Зауважте, що default_random_engineна цих двох платформах абсолютно різне.
TC

1
Це все одно може бути випадковим чином.
Alec Teal

5
Чи кожен програміст проходить фазу, коли вважає, що час є хорошим насінням генератора випадкових чисел?
OldFart

6
@OldFart Так, це називається академія.
Кейсі

Відповіді:


141

Ось що відбувається:

  • default_random_engineу libstdc ++ (стандартна бібліотека GCC) є minstd_rand0, що є простим лінійним конгруентним механізмом:

    typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
  • Спосіб генерації випадкових чисел цим механізмом є x i + 1 = (16807x i + 0) mod 2147483647.

  • Отже, якщо насіння відрізняються на 1, то більшість випадків перше сформоване число буде відрізнятися до 16807.

  • Діапазон даного генератора становить [1, 2147483646]. Спосіб відображення libstdc ++ uniform_int_distributionу ціле число в діапазоні [1, 100] є, по суті, таким: генерує число n. Якщо число не більше 2147483600, поверніть (n - 1) / 21474836 + 1; інакше спробуйте ще раз із новим номером.

    Легко помітити, що у переважній більшості випадків два ns, які відрізняються лише на 16807 р., Дадуть таку ж кількість у [1, 100] за цією процедурою. Насправді можна було б очікувати, що сформоване число збільшиться на одиницю приблизно кожні 21474836/16807 = 1278 секунд або 21,3 хвилини, що досить добре узгоджується з вашими спостереженнями.

MSVC default_random_engineє mt19937, який не має цієї проблеми.


36
Цікаво, чим володіли розробники стандартної бібліотеки GCC для вибору такого жахливого за замовчуванням.
CodesInChaos

13
@CodesInChaos Я не знаю, чи пов'язано це з не, але ланцюжок інструментів MacOS / iOS також використовує той самий жахливий випадковий механізм, завдяки чому rand()% 7 завжди повертає 0
phuclv

7
@ LưuVĩnhPhúc Невиправлення rand()є дещо зрозумілим (це безнадійне застаріле лайно). Використовувати лайновий PRNG для чогось нового не можна вибачити. Я навіть вважаю це стандартним порушенням, оскільки стандарт вимагає "забезпечити принаймні прийнятну поведінку двигуна для відносно випадкового, недосвідченого та / або легкого використання". чого ця реалізація не забезпечує, оскільки вона катастрофічно зазнає невдачі навіть у таких тривіальних випадках використання, як ваш rand % 7приклад.
CodesInChaos

2
@CodesInChaos Чому виправлення не є rand()дещо зрозумілим точно? Чи лише тому, що ніхто не міг подумати це робити?
user253751

2
@immibis API настільки зламаний, що вам краще допомогти незалежна заміна, яка виправляє всі проблеми. 1) Заміна алгоритму була б надзвичайною зміною, тому вам, ймовірно, знадобиться перемикач сумісності для старих програм. 2) Насіння srandзанадто мале, щоб легко генерувати унікальне насіння. 3) Він повертає ціле число з визначеною реалізацією верхньою межею, яку абонент повинен якось зменшити до числа у бажаному діапазоні, що при правильному виконанні є більшою роботою, ніж написання заміни за допомогою розумного API для rand()4) Він використовує глобальний змінний стан
CodesInChaos

30

Визначення std::default_random_engineреалізації. Використовуйте std::mt19937або std::mt19937_64замість цього.

Крім того, std::timeі ctimeфункції не дуже точні, використовуйте <chrono>замість них типи, визначені в заголовку:

#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
Чи бажано використовувати більш точний час при засіванні генератора псевдовипадкових величин? Можливо, це наївно, але здається, що неточність може бути майже бажаною, якщо вона вводить ентропію. (Якщо ви не маєте на увазі, що це менш точно і, отже, істотно менше потенційних насінин.)
Нат,

15
Я б просто запропонував використовувати std::random_deviceзамість current_time для засівання вашого генератора випадкових випадків. Будь ласка, перевірте будь-який приклад cppreference щодо Random.
Олександр Фулар

5
Якщо ви не хочете, щоб хтось вгадував ваше насіння (і, отже, відтворював вашу послідовність), менша точність - це не те саме, що більша випадковість. Давайте підемо до крайності: округлити насіння до наступного дня (або року?) -> вгадати легко. Використовуйте фемтосекундну точність -> Багато
здогадок,

2
@ChemicalEngineer Розмір деталізації ctimeстановить 1 секунду. Розмір std::chronoреалізацій визначається користувачем, за замовчуванням std::high_resolution_clockвін становить (у Visual Studio це typedef std::steady_clock), наносекунд, але може вибрати набагато менший вимір, отже, набагато точніший.
Кейсі

2
@linac Якби ви хотіли криптографічні властивості, ви б використали відповідний prng (не той, що використовується у цій відповіді). І, звичайно, про насіння за часом також не може йтися, незалежно від обіцяної точності.
Ктулху

-2

У Linux випадкова функція - це не випадкова функція в імовірнісному сенсі шляху, а генератор псевдовипадкових чисел. Його солять насінням, і виходячи з цього насіння, отримані числа є псевдовипадковими та рівномірно розподіленими. Спосіб Linux має ту перевагу, що при розробці певних експериментів із використанням інформації з популяцій можна виміряти повторення експерименту з відомими налаштуваннями вхідної інформації. Коли остаточна програма готова до реального тестування, сіль (насіння) можна створити, попросивши користувача перемістити мишу, змішати рух миші з деякими натисканнями клавіш і додати тире мікросекунд з початку останнє включення.

Насіння випадкових чисел Windows отримується з колекції номерів миші, клавіатури, мережі та часу доби. Це не повторюється. Але це значення солі може бути скинуто до відомого насіння, якщо, як уже згадувалося вище, хтось бере участь у розробці експерименту.

О так, Linux має два генератори випадкових чисел. Один, за замовчуванням - за модулем 32 біта, а інший - за модулем 64 біта. Ваш вибір залежить від потреб у точності та кількості обчислювального часу, який ви хочете витратити на тестування або фактичне використання.


5
Я не впевнений, чому ви говорите про алгоритм генерації насіння. OP чітко використовує системний час як насіння. Крім того, чи можете ви додати деякі посилання наcollection of mouse, keyboard, network and time of day numbers
локаль за замовчуванням
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.