ปัญหาพื้นฐานที่สุดของแอปพลิเคชันทดสอบของคุณคือคุณโทรsrand
หนึ่งครั้งแล้วโทรrand
หนึ่งครั้งและออก
จุดทั้งหมดของsrand
ฟังก์ชั่นคือการเริ่มต้นลำดับของตัวเลขสุ่มหลอกด้วยเมล็ดสุ่ม
หมายความว่าหากคุณส่งค่าเดียวกันไปยังsrand
ในสองแอปพลิเคชันที่แตกต่างกัน (ที่มีsrand
/ rand
การใช้งานเดียวกัน) จากนั้นคุณจะได้รับลำดับที่เหมือนกันของrand()
ค่าที่อ่านหลังจากนั้นในทั้งสองแอปพลิเคชัน
อย่างไรก็ตามในตัวอย่างแอปพลิเคชันของคุณลำดับหลอกแบบสุ่มประกอบด้วยองค์ประกอบเดียวเท่านั้นองค์ประกอบแรกของลำดับหลอกแบบสุ่มที่สร้างจากเมล็ดเท่ากับเวลาปัจจุบันของ second
มีความแม่นยำ สิ่งที่คุณคาดหวังที่จะเห็นในการส่งออกนั้น
เห็นได้ชัดว่าเมื่อคุณเรียกใช้แอปพลิเคชันในวินาทีเดียวกัน - คุณใช้ค่าเมล็ดเดียวกัน - ดังนั้นผลลัพธ์ของคุณจึงเป็นแบบเดียวกัน
ที่จริงแล้วคุณควรโทรsrand(seed)
หนึ่งครั้งแล้วโทรrand()
หลายครั้งและวิเคราะห์ลำดับนั้น - มันควรดูสุ่ม
แก้ไข:
อ้อ! ฉันเข้าใจแล้ว. เห็นได้ชัดว่าคำอธิบายด้วยวาจาไม่เพียงพอ (อาจเป็นอุปสรรคด้านภาษาหรืออะไรบางอย่าง ... :))
ตกลง. ตัวอย่างรหัส C แบบเก่าที่ใช้srand()/rand()/time()
ฟังก์ชั่นเดียวกันกับที่ใช้ในคำถาม:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ ที่ลำดับจากการทำงานครั้งเดียวของโปรแกรมที่ควรจะมีลักษณะแบบสุ่ม
EDIT2:
เมื่อใช้ไลบรารีมาตรฐาน C หรือ C ++ สิ่งสำคัญคือต้องเข้าใจว่า ณ ตอนนี้ไม่มีฟังก์ชั่นมาตรฐานเดียวหรือคลาสที่สร้างข้อมูลสุ่มแบบสุ่มแน่นอน (รับประกันโดยมาตรฐาน) เครื่องมือมาตรฐานเดียวที่เข้าถึงปัญหานี้คือstd :: random_deviceแต่น่าเสียดายที่ยังไม่ได้รับประกันการสุ่มจริง
ขึ้นอยู่กับลักษณะของแอปพลิเคชันคุณควรตัดสินใจก่อนว่าคุณต้องการข้อมูลที่สุ่ม (คาดเดาไม่ได้) จริงๆหรือไม่ กรณีเด่นเมื่อคุณทำแน่นอนที่สุดต้องสุ่มตัวอย่างที่แท้จริงคือความปลอดภัยของข้อมูล - เช่นการสร้างคีย์สมมาตร, คีย์ส่วนตัวแบบอสมมาตร, ค่าเกลือ, โทเค็นความปลอดภัย ฯลฯ
อย่างไรก็ตามตัวเลขสุ่มระดับความปลอดภัยเป็นอุตสาหกรรมที่แยกต่างหากที่คุ้มค่าบทความแยกต่างหาก
ในกรณีส่วนใหญ่เครื่องสร้างตัวเลขสุ่มหลอกก็เพียงพอแล้วเช่นสำหรับการจำลองทางวิทยาศาสตร์หรือเกม ในบางกรณีจำเป็นต้องมีการเรียงลำดับแบบสุ่มหลอกอย่างสม่ำเสมอเช่นในเกมที่คุณอาจเลือกที่จะสร้างแผนที่เดียวกันในรันไทม์เพื่อหลีกเลี่ยงการเก็บข้อมูลจำนวนมาก
คำถามดั้งเดิมและคำถามซ้ำ ๆ มากมายที่เหมือนกัน / คล้ายคลึงกัน (และแม้แต่คำตอบที่ "เข้าใจผิด" หลายข้อ) บ่งชี้ว่าสิ่งแรกและสำคัญที่สุดคือการแยกแยะตัวเลขสุ่มจากตัวเลขสุ่มหลอกและเพื่อให้เข้าใจว่าอะไรคือตัวเลขสุ่มหลอก สถานที่แรกและที่จะตระหนักว่ากำเนิดตัวเลขสุ่มหลอกไม่ได้ใช้วิธีเดียวกับที่คุณสามารถใช้กำเนิดตัวเลขสุ่มจริง
โดยสังหรณ์ใจเมื่อคุณขอหมายเลขสุ่ม - ผลลัพธ์ที่ส่งคืนไม่ควรขึ้นอยู่กับค่าที่ส่งคืนก่อนหน้านี้และไม่ควรขึ้นอยู่กับว่าใครขออะไรมาก่อนและไม่ควรขึ้นอยู่กับช่วงเวลาใดและกระบวนการใดและคอมพิวเตอร์เครื่องใด มันต้องการกาแล็คซี่อะไร นั่นคือสิ่งที่คำว่า"สุ่ม"หมายถึงหลังจากทั้งหมด - การคาดเดาไม่ได้และเป็นอิสระจากอะไร - ไม่อย่างนั้นมันจะไม่สุ่มอีกต่อไปใช่ไหม? ด้วยสัญชาตญาณนี้มันเป็นเรื่องธรรมดาที่จะค้นหาเวทย์มนตร์บางเว็บเพื่อใช้ในการรับตัวเลขสุ่มในบริบทที่เป็นไปได้
^^^ ความคาดหวังแบบสัญชาตญาณแบบนั้นผิดมากและเป็นอันตรายในทุกกรณีที่เกี่ยวข้องกับเครื่องกำเนิดตัวเลขแบบหลอก - แม้จะมีเหตุผลสำหรับตัวเลขสุ่มจริง
ในขณะที่ความคิดที่มีความหมายของ "หมายเลขสุ่ม" มีอยู่ - ไม่มีสิ่งเช่น "หมายเลขสุ่มหลอก" Pseudo-Random Number Generatorจริงผลิตจำนวนสุ่มหลอกลำดับ
เมื่อผู้เชี่ยวชาญพูดถึงคุณภาพของ PRNG พวกเขาจะพูดถึงคุณสมบัติทางสถิติของลำดับที่สร้างขึ้น (และลำดับย่อยที่น่าทึ่ง) ตัวอย่างเช่นหากคุณรวม PRNG คุณภาพสูงสองรายการโดยใช้ทั้งสองอย่างในทางกลับกัน - คุณอาจสร้างลำดับผลลัพธ์ที่ไม่ดี - แม้ว่าพวกเขาจะสร้างลำดับที่ดีแยกจากกัน (ทั้งสองลำดับที่ดีเหล่านั้นอาจสัมพันธ์กัน
ในความเป็นจริงการเรียงลำดับแบบสุ่มหลอกเป็นจริงเสมอกำหนด (กำหนดไว้ล่วงหน้าโดยอัลกอริทึมและพารามิเตอร์เริ่มต้น) นั่นคือไม่มีอะไรสุ่มเกี่ยวกับมัน
เฉพาะrand()
/ srand(s)
คู่ของฟังก์ชั่นให้เอกพจน์ต่อกระบวนการที่ไม่ปลอดภัยเธรด (!) ลำดับเลขสุ่มหลอกที่สร้างขึ้นด้วยอัลกอริทึมที่กำหนดใช้งาน ฟังก์ชั่นการผลิตค่าในช่วงrand()
[0, RAND_MAX]
อ้างอิงจากมาตรฐาน C11:
ฟังก์ชั่นใช้อาร์กิวเมนต์เป็นเมล็ดพันธุ์สำหรับลำดับใหม่ของตัวเลขสุ่มหลอกที่จะส่งกลับโดยโทรตามมาsrand
rand
หาก
srand
มีการเรียกด้วยค่าเมล็ดเดียวกันลำดับของตัวเลขสุ่มหลอกจะต้องทำซ้ำ หากrand
ถูกเรียกก่อนที่จะทำการเรียกใด ๆsrand
ลำดับเดียวกันจะถูกสร้างขึ้นเช่นเดียวกับเมื่อsrand
ถูกเรียกครั้งแรกด้วยค่าเมล็ด 1
หลายคนคาดหวังอย่างสมเหตุสมผลว่า rand()
จะผลิตลำดับของกึ่งอิสระกระจายสม่ำเสมอตัวเลขในช่วงที่จะ0
RAND_MAX
ดีมันแน่นอนที่สุดควร (มิฉะนั้นจะไร้ประโยชน์) แต่น่าเสียดายที่ไม่ได้เป็นเพียงมาตรฐานไม่จำเป็นต้องให้ - มีแม้กระทั่งข้อจำกัดความรับผิดชอบอย่างชัดเจนว่ารัฐ"มีการค้ำประกันไม่เป็นคุณภาพของลำดับสุ่มผลิต" ในบางกรณีในอดีตrand
/ srand
การใช้งานมีคุณภาพไม่ดีอย่างแน่นอน แม้ว่าในการติดตั้งใช้งานที่ทันสมัยมันก็น่าจะดีพอ - แต่ความเชื่อใจนั้นขาดและไม่สามารถกู้คืนได้ นอกเหนือจากลักษณะที่ไม่ปลอดภัยของเธรดทำให้การใช้งานที่ปลอดภัยในแอพพลิเคชั่นแบบมัลติเธรดนั้นมีความยุ่งยากและ จำกัด (ยังเป็นไปได้ - คุณสามารถใช้งานได้จากเธรดเฉพาะหนึ่งเธรด)
เทมเพลตคลาสใหม่ std :: mersenne_twister_engine <> (และความสะดวกในการพิมพ์ - std::mt19937
/ std::mt19937_64
พร้อมกับแม่แบบพารามิเตอร์ที่ดี) ให้ตัวสร้างตัวเลขแบบหลอกต่อวัตถุที่กำหนดไว้ในมาตรฐาน C ++ 11 ด้วยพารามิเตอร์แม่แบบเดียวกันและพารามิเตอร์การเริ่มต้นเดียวกันวัตถุที่แตกต่างกันจะสร้างลำดับผลลัพธ์ต่อวัตถุเดียวกันบนคอมพิวเตอร์ทุกเครื่องในแอปพลิเคชันใด ๆ ที่สร้างด้วยไลบรารีมาตรฐานที่สอดคล้องกับ C ++ 11 ข้อได้เปรียบของคลาสนี้คือคุณภาพเอาต์พุตที่คาดการณ์ได้สูงและมีความสม่ำเสมอในการใช้งาน
นอกจากนี้ยังมีเอ็นจิน PRNG เพิ่มเติมที่กำหนดไว้ในมาตรฐาน C ++ 11 - มาตรฐาน :: linear_congruential_engine <> (อดีตเคยเป็นที่มีคุณภาพเป็นธรรมsrand/rand
อัลกอริทึมในบาง C การใช้งานห้องสมุดมาตรฐาน) และมาตรฐาน :: subtract_with_carry_engine <> พวกเขายังสร้างลำดับขึ้นอยู่กับพารามิเตอร์ที่กำหนดขึ้นอย่างเต็มที่ต่อลำดับวัตถุ
Modern day C ++ 11 ตัวอย่างการแทนที่รหัส C ที่ล้าสมัยด้านบน:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
รุ่นของรหัสก่อนหน้าที่ใช้std :: uniform_int_distribution <>
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}