นี่คือการติดตามจากคำถามที่โพสต์ไว้ก่อนหน้านี้:
จะสร้างตัวเลขสุ่มใน C ได้อย่างไร?
ฉันต้องการสร้างตัวเลขสุ่มจากภายในช่วงที่กำหนดเช่น 1 ถึง 6 เพื่อเลียนแบบด้านข้างของดาย
ฉันจะทำสิ่งนี้ได้อย่างไร
นี่คือการติดตามจากคำถามที่โพสต์ไว้ก่อนหน้านี้:
จะสร้างตัวเลขสุ่มใน C ได้อย่างไร?
ฉันต้องการสร้างตัวเลขสุ่มจากภายในช่วงที่กำหนดเช่น 1 ถึง 6 เพื่อเลียนแบบด้านข้างของดาย
ฉันจะทำสิ่งนี้ได้อย่างไร
คำตอบ:
คำตอบทั้งหมดจนถึงขณะนี้ผิดทางคณิตศาสตร์ การส่งคืนrand() % N
ไม่ได้ให้ตัวเลขในช่วงอย่างสม่ำเสมอ[0, N)
เว้นแต่จะN
แบ่งความยาวของช่วงเวลาที่rand()
จะส่งกลับ (กล่าวคือยกกำลัง 2) นอกจากนี้ไม่มีใครรู้ว่าโมดูลิของrand()
เป็นอิสระหรือไม่: เป็นไปได้ว่ามันไป0, 1, 2, ...
ซึ่งเหมือนกัน แต่ไม่สุ่มมากนัก สมมติฐานเดียวที่ดูสมเหตุสมผลคือrand()
การแจกแจงแบบปัวซอง: ช่วงย่อยที่ไม่ซ้อนทับสองช่วงที่มีขนาดเท่ากันมีโอกาสเท่ากันและเป็นอิสระ สำหรับชุดค่าที่ จำกัด หมายถึงการแจกแจงแบบสม่ำเสมอและยังช่วยให้มั่นใจได้ว่าค่าของrand()
จะกระจัดกระจายอย่างสวยงาม
ซึ่งหมายความว่าวิธีเดียวที่ถูกต้องในการเปลี่ยนช่วงrand()
คือการแบ่งออกเป็นกล่อง ตัวอย่างเช่นหากRAND_MAX == 11
คุณต้องการช่วง1..6
คุณควรกำหนดให้{0,1}
เป็น 1 {2,3}
ถึง 2 และอื่น ๆ สิ่งเหล่านี้ไม่ปะติดปะต่อช่วงเวลาที่มีขนาดเท่ากันและกระจายอย่างสม่ำเสมอและเป็นอิสระ
ข้อเสนอแนะในการใช้การแบ่งจุดลอยตัวมีความเป็นไปได้ทางคณิตศาสตร์ แต่โดยหลักการแล้วต้องทนทุกข์ทรมานจากการปัดเศษ บางทีอาจจะdouble
เป็นความแม่นยำสูงมากพอที่จะทำให้การทำงาน; อาจจะไม่. ฉันไม่รู้และฉันไม่ต้องการที่จะคิดออก ไม่ว่าในกรณีใดคำตอบขึ้นอยู่กับระบบ
วิธีที่ถูกต้องคือการใช้เลขคณิตจำนวนเต็ม นั่นคือคุณต้องการสิ่งต่อไปนี้:
#include <stdlib.h> // For random(), RAND_MAX
// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
unsigned long
// max <= RAND_MAX < ULONG_MAX, so this is okay.
num_bins = (unsigned long) max + 1,
num_rand = (unsigned long) RAND_MAX + 1,
bin_size = num_rand / num_bins,
defect = num_rand % num_bins;
long x;
do {
x = random();
}
// This is carefully written not to overflow
while (num_rand - defect <= (unsigned long)x);
// Truncated division is intentional
return x/bin_size;
}
ห่วงเป็นสิ่งจำเป็นเพื่อให้ได้การกระจายที่สม่ำเสมออย่างสมบูรณ์แบบ ตัวอย่างเช่นหากคุณได้รับหมายเลขสุ่มจาก 0 ถึง 2 และคุณต้องการเพียงตัวเลขจาก 0 ถึง 1 คุณก็ดึงไปเรื่อย ๆ จนกว่าจะไม่ได้ 2 ไม่ยากที่จะตรวจสอบว่าสิ่งนี้ให้ 0 หรือ 1 โดยมีความน่าจะเป็นเท่ากัน วิธีนี้ยังอธิบายไว้ในลิงก์ที่ nos ให้ไว้ในคำตอบแม้ว่าจะเข้ารหัสต่างกัน ฉันใช้random()
มากกว่าrand()
เนื่องจากมีการกระจายที่ดีกว่า (ตามที่ระบุไว้ใน man page สำหรับrand()
)
หากคุณต้องการรับค่าสุ่มนอกช่วงเริ่มต้น[0, RAND_MAX]
คุณต้องทำสิ่งที่ยุ่งยาก บางทีสมควรที่สุดคือการกำหนดฟังก์ชั่นrandom_extended()
ที่ดึงn
บิต (ใช้random_at_most()
) และผลตอบแทนใน[0, 2**n)
และจากนั้นให้ใช้random_at_most()
กับrandom_extended()
ในสถานที่ของrandom()
(และ2**n - 1
ในสถานที่ของRAND_MAX
) เพื่อดึงค่าสุ่มน้อยกว่า2**n
สมมติว่าคุณมีประเภทตัวเลขที่สามารถถือดังกล่าว ค่า ในที่สุดแน่นอนคุณจะได้รับค่าในการ[min, max]
ใช้min + random_at_most(max - min)
รวมถึงค่าลบ
max - min > RAND_MAX
ซึ่งร้ายแรงกว่าปัญหาที่ฉันระบุไว้ข้างต้น (เช่น VC ++ มีRAND_MAX
เพียง 32767)
do {} while()
.
จากคำตอบของ @Ryan Reich ฉันคิดว่าฉันจะเสนอเวอร์ชันที่ล้างแล้ว การตรวจสอบขอบเขตแรกไม่จำเป็นต้องใช้ในการตรวจสอบขอบเขตครั้งที่สองและฉันได้ทำซ้ำแล้วซ้ำอีกแทนที่จะเรียกซ้ำ ก็จะส่งกลับค่าในช่วง [นาที, สูงสุด] ที่และmax >= min
1+max-min < RAND_MAX
unsigned int rand_interval(unsigned int min, unsigned int max)
{
int r;
const unsigned int range = 1 + max - min;
const unsigned int buckets = RAND_MAX / range;
const unsigned int limit = buckets * range;
/* Create equal size buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
do
{
r = rand();
} while (r >= limit);
return min + (r / buckets);
}
limit
เป็น int (และเลือกที่bucket
มากเกินไป) ตั้งแต่RAND_MAX / range
< INT_MAX
และ<=buckets * range
RAND_MAX
แก้ไข: ฉันได้ส่งและแก้ไขข้อเสนอแล้ว
นี่คือสูตรหากคุณทราบค่าสูงสุดและต่ำสุดของช่วงและคุณต้องการสร้างตัวเลขที่รวมอยู่ระหว่างช่วง:
r = (rand() % (max + 1 - min)) + min
int
max+1-min
unsigned int
randr(unsigned int min, unsigned int max)
{
double scaled = (double)rand()/RAND_MAX;
return (max - min +1)*scaled + min;
}
ดูตัวเลือกอื่น ๆ ได้ที่นี่
(((max-min+1)*rand())/RAND_MAX)+min
และรับการแจกแจงแบบเดียวกันได้อย่างง่ายดาย(สมมติว่า RAND_MAX มีขนาดเล็กพอเมื่อเทียบกับ int เพื่อไม่ให้ล้น)
max + 1
ถ้าอย่างใดอย่างหนึ่งrand() == RAND_MAX
หรือrand()
อยู่ใกล้กับและข้อผิดพลาดจุดลอยตัวผลักดันผลสุดท้ายที่ผ่านมาRAND_MAX
max + 1
เพื่อความปลอดภัยคุณควรตรวจสอบว่าผลลัพธ์อยู่ในระยะก่อนส่งคืน
RAND_MAX + 1.0
. ฉันยังไม่แน่ใจว่ามันดีพอที่จะป้องกันmax + 1
ผลตอบแทนได้แม้ว่าโดยเฉพาะอย่างยิ่ง+ min
ในตอนท้ายนั้นเกี่ยวข้องกับรอบที่อาจทำให้เกิดmax + 1
ค่า Rand () จำนวนมาก ปลอดภัยกว่าที่จะละทิ้งแนวทางนี้โดยสิ้นเชิงและใช้เลขคณิตจำนวนเต็ม
RAND_MAX
จะถูกแทนที่ด้วยRAND_MAX+1.0
ขณะที่คริสโตแสดงให้เห็นแล้วผมเชื่อว่าสิ่งนี้มีความปลอดภัยโดยมีเงื่อนไขว่าจะกระทำโดยใช้จำนวนเต็มคณิตศาสตร์:+ min
return (unsigned int)((max - min + 1) * scaled) + min
เหตุผล (ไม่ชัดเจน) ก็คือสมมติว่า IEEE 754 เลขคณิตและครึ่งวงกลมถึงคู่ (และยังmax - min + 1
สามารถแทนค่าได้ว่าเป็นคู่ แต่จะเป็นจริงในเครื่องทั่วไป) มันเป็นความจริงเสมอx * scaled < x
สำหรับ บวกใด ๆ คู่x
และคู่ใด ๆที่น่าพอใจscaled
0.0 <= scaled && scaled < 1.0
randr(0, UINT_MAX)
: สร้าง 0 เสมอ
คุณจะไม่ทำ:
srand(time(NULL));
int r = ( rand() % 6 ) + 1;
%
เป็นตัวดำเนินการโมดูลัส โดยพื้นฐานแล้วมันจะหารด้วย 6 และส่งกลับส่วนที่เหลือ ... จาก 0 - 5
rand()
มีบิตลำดับต่ำของสถานะของเครื่องกำเนิดไฟฟ้า (ถ้าใช้ LCG) ฉันยังไม่เคยเห็นเลย - ทั้งหมด (ใช่รวมถึง MSVC ที่ RAND_MAX เป็นเพียง 32767) ลบบิตลำดับต่ำออก ไม่แนะนำให้ใช้โมดูลัสด้วยเหตุผลอื่นกล่าวคือทำให้การแจกแจงเบ้ไปตามจำนวนที่น้อยกว่า
สำหรับผู้ที่เข้าใจปัญหาอคติ แต่ไม่สามารถทนต่อเวลาทำงานที่ไม่สามารถคาดเดาได้ของวิธีการที่ใช้การปฏิเสธชุดนี้จะสร้างจำนวนเต็มสุ่มแบบเอนเอียงน้อยลงใน[0, n-1]
ช่วงเวลา:
r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...
มันไม่ได้โดย synthesising ความแม่นยำสูงคงที่จุดสุ่มi * log_2(RAND_MAX + 1)
บิต (ซึ่งi
คือจำนวนของการทำซ้ำ) n
และการดำเนินการคูณยาวจาก
เมื่อจำนวนบิตมากพอเมื่อเทียบกับn
อคติจะมีขนาดเล็กมาก
ไม่สำคัญว่าRAND_MAX + 1
จะน้อยกว่าn
(เช่นในคำถามนี้ ) หรือไม่ใช่ยกกำลังสอง แต่ต้องใช้ความระมัดระวังเพื่อหลีกเลี่ยงการล้นจำนวนเต็มหากRAND_MAX * n
มีขนาดใหญ่
RAND_MAX
มักจะเป็นINT_MAX
เช่นนั้นRAND_MAX + 1
-> UB (เช่น INT_MIN)
RAND_MAX * n
มีขนาดใหญ่" คุณต้องจัดให้ใช้ประเภทที่เหมาะสมกับความต้องการของคุณ
RAND_MAX
มักจะINT_MAX
" ใช่ แต่ในระบบ 16 บิตเท่านั้น! การเก็บถาวรที่ทันสมัยพอสมควรจะใส่ไว้INT_MAX
ที่ 2 ^ 32/2 และRAND_MAX
ที่ 2 ^ 16/2 เป็นสมมติฐานที่ไม่ถูกต้องหรือไม่?
int
คอมไพเลอร์32 บิตฉันพบRAND_MAX == 32767
ในหนึ่งและRAND_MAX == 2147483647
ในอีกอันหนึ่ง ประสบการณ์โดยรวมของฉัน (ทศวรรษ) นั้น RAND_MAX == INT_MAX
บ่อยขึ้น ดังนั้นไม่เห็นด้วยที่สถาปัตยกรรม 32 บิตที่ทันสมัยพอสมควรจะมีRAND_MAX
ที่2^16 / 2
. เนื่องจากข้อมูลจำเพาะ C อนุญาต32767 <= RAND_MAX <= INT_MAX
ฉันจึงเขียนโค้ดไปตามนั้นแทนที่จะเป็นแนวโน้ม
เพื่อหลีกเลี่ยงความลำเอียงของโมดูโล (แนะนำในคำตอบอื่น ๆ ) คุณสามารถใช้:
arc4random_uniform(MAX-MIN)+MIN
โดยที่ "MAX" คือขอบเขตบนและ "MIN" คือขอบเขตล่าง ตัวอย่างเช่นสำหรับตัวเลขระหว่าง 10 ถึง 20:
arc4random_uniform(20-10)+10
arc4random_uniform(10)+10
วิธีง่ายๆและดีกว่าการใช้ "rand ()% N"
#include <bsd/stdlib.h>
ก่อนอื่น นอกจากนี้ความคิดใด ๆ ที่จะรับสิ่งนี้บน Windows โดยไม่ใช้ MinGW หรือ CygWin?
นี่คืออัลกอริทึมที่ง่ายกว่าโซลูชันของ Ryan Reich เล็กน้อย:
/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
uint32_t range = (end - begin) + 1;
uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);
/* Imagine range-sized buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
uint32_t randVal = rand();
while (randVal >= limit) randVal = rand();
/// Return the position you hit in the bucket + begin as random number
return (randVal % range) + begin;
}
Example (RAND_MAX := 16, begin := 2, end := 7)
=> range := 6 (1 + end - begin)
=> limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)
The limit is always a multiple of the range,
so we can split it into range-sized buckets:
Possible-rand-output: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Buckets: [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
Buckets + begin: [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]
1st call to rand() => 13
→ 13 is not in the bucket-range anymore (>= limit), while-condition is true
→ retry...
2nd call to rand() => 7
→ 7 is in the bucket-range (< limit), while-condition is false
→ Get the corresponding bucket-value 1 (randVal % range) and add begin
=> 3
RAND_MAX + 1
สามารถint
เติมล้นได้อย่างง่ายดาย ในกรณีนั้น(RAND_MAX + 1) % range
จะสร้างผลลัพธ์ที่น่าสงสัย พิจารณา(RAND_MAX + (uint32_t)1)
แม้ว่า Ryan จะถูกต้อง แต่การแก้ปัญหาอาจง่ายกว่ามากโดยอาศัยสิ่งที่ทราบเกี่ยวกับแหล่งที่มาของการสุ่ม ในการระบุปัญหาอีกครั้ง:
[0, MAX)
มีการแจกแจงแบบสม่ำเสมอ[rmin, rmax]
0 <= rmin < rmax < MAX
จากประสบการณ์ของฉันถ้าจำนวนถังขยะ (หรือ "กล่อง") มีขนาดเล็กกว่าช่วงของตัวเลขดั้งเดิมอย่างมีนัยสำคัญและแหล่งที่มาดั้งเดิมมีความแข็งแกร่งในการเข้ารหัส - ไม่จำเป็นต้องทำผ่าน rigamarole ทั้งหมดและการแบ่งโมดูโลอย่างง่ายจะ เพียงพอ (เช่นoutput = rnd.next() % (rmax+1)
ถ้าrmin == 0
) และสร้างตัวเลขสุ่มที่กระจายอย่างสม่ำเสมอ "เพียงพอ" และไม่มีการสูญเสียความเร็วใด ๆ ปัจจัยสำคัญคือแหล่งที่มาของการสุ่ม (เช่นเด็ก ๆ อย่าลองทำที่บ้านด้วยrand()
)
นี่คือตัวอย่าง / หลักฐานว่ามันทำงานอย่างไรในทางปฏิบัติ ฉันต้องการสร้างตัวเลขสุ่มตั้งแต่ 1 ถึง 22 โดยมีแหล่งที่มาที่แข็งแกร่งในการเข้ารหัสที่สร้างไบต์แบบสุ่ม (อิงจาก Intel RDRAND) ผลลัพธ์คือ:
Rnd distribution test (22 boxes, numbers of entries in each box): 1: 409443 4.55% 2: 408736 4.54% 3: 408557 4.54% 4: 409125 4.55% 5: 408812 4.54% 6: 409418 4.55% 7: 408365 4.54% 8: 407992 4.53% 9: 409262 4.55% 10: 408112 4.53% 11: 409995 4.56% 12: 409810 4.55% 13: 409638 4.55% 14: 408905 4.54% 15: 408484 4.54% 16: 408211 4.54% 17: 409773 4.55% 18: 409597 4.55% 19: 409727 4.55% 20: 409062 4.55% 21: 409634 4.55% 22: 409342 4.55% total: 100.00%
สิ่งนี้ใกล้เคียงกับเครื่องแบบเท่าที่ฉันต้องการสำหรับจุดประสงค์ของฉัน (การโยนลูกเต๋าที่ยุติธรรมการสร้างโค้ดบุ๊กที่มีการเข้ารหัสที่แข็งแกร่งสำหรับเครื่องเข้ารหัสสมัยสงครามโลกครั้งที่สองเช่นhttp://users.telenet.be/d.rijmenants/en/kl-7sim.htmฯลฯ ). ผลลัพธ์ไม่แสดงอคติใด ๆ ที่เห็นได้ชัด
นี่คือที่มาของตัวสร้างตัวเลขสุ่มที่แข็งแกร่ง (จริง) ที่เข้ารหัสลับ: Intel Digital Random Number Generator และโค้ดตัวอย่างที่สร้างตัวเลขสุ่ม 64 บิต (ไม่ได้ลงชื่อ)
int rdrand64_step(unsigned long long int *therand)
{
unsigned long long int foo;
int cf_error_status;
asm("rdrand %%rax; \
mov $1,%%edx; \
cmovae %%rax,%%rdx; \
mov %%edx,%1; \
mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
*therand = foo;
return cf_error_status;
}
ฉันรวบรวมบน Mac OS X ด้วย clang-6.0.1 (ตรง) และด้วย gcc-4.8.3 โดยใช้แฟล็ก "-Wa, q" (เนื่องจาก GAS ไม่รองรับคำแนะนำใหม่เหล่านี้)
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 บน Ubuntu 16) หรือclang randu.c -o randu
(Clang 3.8.0) ใช้งานได้ แต่ทิ้งคอร์ที่รันไทม์ด้วยIllegal instruction (core dumped)
. ความคิดใด ๆ ?
rand()
สิ่งที่ฉันยังไม่สามารถรับคือสิ่งที่เป็นไปอย่างผิดปกติกับ ฉันลองทดสอบและโพสต์คำถามนี้แล้วแต่ยังหาคำตอบที่ชัดเจนไม่ได้
อย่างที่กล่าวไว้ก่อนหน้านี้ว่าโมดูโลไม่เพียงพอเพราะมันทำให้การกระจายตัวเบ้ นี่คือรหัสของฉันซึ่งปิดบังบิตและใช้เพื่อให้แน่ใจว่าการกระจายไม่บิดเบี้ยว
static uint32_t randomInRange(uint32_t a,uint32_t b) {
uint32_t v;
uint32_t range;
uint32_t upper;
uint32_t lower;
uint32_t mask;
if(a == b) {
return a;
}
if(a > b) {
upper = a;
lower = b;
} else {
upper = b;
lower = a;
}
range = upper - lower;
mask = 0;
//XXX calculate range with log and mask? nah, too lazy :).
while(1) {
if(mask >= range) {
break;
}
mask = (mask << 1) | 1;
}
while(1) {
v = rand() & mask;
if(v <= range) {
return lower + v;
}
}
}
รหัสง่ายๆต่อไปนี้ให้คุณดูการกระจาย:
int main() {
unsigned long long int i;
unsigned int n = 10;
unsigned int numbers[n];
for (i = 0; i < n; i++) {
numbers[i] = 0;
}
for (i = 0 ; i < 10000000 ; i++){
uint32_t rand = random_in_range(0,n - 1);
if(rand >= n){
printf("bug: rand out of range %u\n",(unsigned int)rand);
return 1;
}
numbers[rand] += 1;
}
for(i = 0; i < n; i++) {
printf("%u: %u\n",i,numbers[i]);
}
}
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
ฉันเข้าใจว่า modulo นั้นทำงานได้ช้ากว่าการมาสก์ แต่ฉันก็ยังคิดว่า ..... มันควรจะทดสอบ
rand()
ส่งกลับในช่วงint
[0..RAND_MAX]
ช่วงนั้นสามารถเป็นช่วงย่อยได้อย่างง่ายดายuint32_t
และrandomInRange(0, ,b)
ไม่สร้างค่าในช่วง(INT_MAX...b]
นั้น
จะส่งคืนตัวเลขทศนิยมในช่วง [0,1]:
#define rand01() (((double)random())/((double)(RAND_MAX)))