<random> สร้างหมายเลขเดียวกันใน Linux แต่ไม่ใช่ใน Windows


91

รหัสด้านล่างนี้มีไว้เพื่อสร้างรายการตัวเลขสุ่มหลอกห้าหมายเลขในช่วงเวลา [1,100] ฉันเมล็ดdefault_random_engineด้วยtime(0)ซึ่งจะส่งกลับเวลาของระบบในเวลายูนิกซ์ เมื่อฉันรวบรวมและเรียกใช้โปรแกรมนี้บน 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)?
Mark Ransom

3
โปรดทราบว่าdefault_random_engineทั้งสองแพลตฟอร์มนั้นแตกต่างกันอย่างสิ้นเชิง
TC

1
มันยังคงสุ่ม BTW ได้
Alec Teal

5
โปรแกรมเมอร์ทุกคนต้องผ่านขั้นตอนที่พวกเขาคิดว่าเวลาเป็นเครื่องกำเนิดตัวเลขสุ่มที่ดีหรือไม่?
OldFart

6
@OldFart ใช่เรียกว่าวิชาการ
Casey

คำตอบ:


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 ++ 's uniform_int_distributionแผนที่มันเป็นจำนวนเต็มในช่วง [1, 100] nเป็นหลักนี้สร้างจำนวน ถ้าจำนวนไม่เกิน 2147483600 แล้วกลับ(n - 1) / 21474836 + 1; มิฉะนั้นลองอีกครั้งโดยใช้หมายเลขใหม่

    มันควรจะง่ายที่จะเห็นว่าในกรณีส่วนใหญ่สองnวินาทีที่แตกต่างกันเพียง 16807 จะให้ตัวเลขเดียวกันใน [1, 100] ภายใต้ขั้นตอนนี้ ในความเป็นจริงเราคาดหวังว่าจำนวนที่สร้างขึ้นจะเพิ่มขึ้นหนึ่งในทุกๆ 21474836/16807 = 1278 วินาทีหรือ 21.3 นาทีซึ่งก็เห็นด้วยกับการสังเกตของคุณ

MSVC default_random_engineคือmt19937ซึ่งไม่มีปัญหานี้


36
ฉันสงสัยว่าอะไรที่ทำให้นักพัฒนาของไลบรารีมาตรฐานของ GCC เลือกค่าเริ่มต้นที่น่ากลัวเช่นนี้
CodesInChaos

13
@CodesInChaos ฉันไม่รู้ว่ามันเกี่ยวข้องกันหรือเปล่า แต่ toolchain ของ MacOS / iOS ยังใช้rand()
เอนจิ้น

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
Aleksander Fular

5
หากคุณไม่ต้องการให้ใครเดาเมล็ดพันธุ์ของคุณ (ดังนั้นจึงสร้างลำดับของคุณขึ้นมาใหม่) ความแม่นยำที่น้อยลงจะไม่เหมือนกับการสุ่มมากขึ้น ไปให้สุดขีด: ปัดเมล็ดของคุณไปยังวันถัดไป (หรือปี?) -> การคาดเดาเป็นเรื่องง่าย ใช้ความแม่นยำของ femtosecond -> การคาดเดามากมายที่จะทำ ...
linac

2
@ChemicalEngineer ความละเอียดctimeคือ 1 วินาที ความละเอียดของstd::chronoการนำไปใช้งานเป็นแบบที่ผู้ใช้กำหนดโดยค่าเริ่มต้นสำหรับstd::high_resolution_clock(ใน Visual Studio เป็นตัวพิมพ์เล็กสำหรับstd::steady_clock) นาโนวินาที แต่สามารถเลือกการวัดที่เล็กกว่ามากดังนั้นจึงแม่นยำกว่ามาก
Casey

2
@linac หากคุณต้องการคุณสมบัติการเข้ารหัสคุณจะใช้ prng ที่เหมาะสม (ไม่ใช่อันที่ใช้ในคำตอบนี้) และแน่นอนว่าเมล็ดพันธุ์ตามเวลาก็ไม่เป็นปัญหาเช่นกันไม่ว่าจะเป็นความแม่นยำตามสัญญาก็ตาม
Cthulhu

-2

ในลินุกซ์ฟังก์ชันสุ่มไม่ใช่ฟังก์ชันสุ่มในแง่ของความน่าจะเป็น แต่เป็นตัวสร้างตัวเลขสุ่มหลอก มันเค็มด้วยเมล็ดและขึ้นอยู่กับเมล็ดพันธุ์นั้นตัวเลขที่ผลิตได้จะสุ่มหลอกและกระจายอย่างสม่ำเสมอ วิธีของลินุกซ์มีข้อได้เปรียบในการออกแบบการทดลองบางอย่างโดยใช้ข้อมูลจากประชากรซึ่งสามารถวัดการทำซ้ำของการทดลองด้วยการปรับแต่งข้อมูลอินพุตที่ทราบได้ เมื่อโปรแกรมขั้นสุดท้ายพร้อมสำหรับการทดสอบในชีวิตจริงเกลือ (เมล็ดพันธุ์) สามารถสร้างขึ้นได้โดยขอให้ผู้ใช้เลื่อนเมาส์ผสมการเคลื่อนไหวของเมาส์กับการกดแป้นพิมพ์บางส่วนและเพิ่มจำนวนไมโครวินาทีนับตั้งแต่จุดเริ่มต้นของ เปิดเครื่องครั้งสุดท้าย

เมล็ดพันธุ์ตัวเลขสุ่มของ Windows ได้มาจากการรวบรวมเมาส์แป้นพิมพ์เครือข่ายและตัวเลขเวลาของวัน ไม่สามารถทำซ้ำได้ แต่ค่าเกลือนี้อาจถูกรีเซ็ตเป็นเมล็ดที่ทราบหากตามที่กล่าวไว้ข้างต้นค่าหนึ่งมีส่วนร่วมในการออกแบบการทดลอง

โอ้ใช่ลินุกซ์มีตัวสร้างตัวเลขสุ่มสองตัว หนึ่งค่าเริ่มต้นคือโมดูโล 32 บิตและอีกค่าหนึ่งคือโมดูโล 64 บิต ทางเลือกของคุณขึ้นอยู่กับความต้องการความแม่นยำและระยะเวลาในการประมวลผลที่คุณต้องการใช้สำหรับการทดสอบหรือการใช้งานจริง


5
ฉันไม่แน่ใจว่าทำไมคุณถึงพูดถึงอัลกอริทึมการสร้างเมล็ดพันธุ์ OP ใช้เวลาของระบบเป็นเมล็ดพันธุ์อย่างชัดเจน นอกจากนี้คุณสามารถเพิ่มการอ้างอิงบางส่วนในcollection of mouse, keyboard, network and time of day numbers
ภาษาเริ่มต้น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.