วิธีการสร้าง int สุ่มใน C?


548

มีฟังก์ชั่นในการสร้างหมายเลข int แบบสุ่มใน C หรือไม่? หรือฉันจะต้องใช้ห้องสมุดบุคคลที่สาม?


1
หัวข้อยาวใน comp.lang.c เกี่ยวกับแรนด์ () และ PRNG "คุณภาพ"
luser droog

2
ดูเพิ่มเติมsrand: ทำไมเรียกว่าเพียงครั้งเดียว
Jonathan Leffler

คำตอบ:


662

หมายเหตุ : อย่าใช้rand()เพื่อความปลอดภัย หากคุณต้องการหมายเลขที่ปลอดภัยเข้ารหัสให้ดูคำตอบนี้แทน

#include <time.h>
#include <stdlib.h>

srand(time(NULL));   // Initialization, should only be called once.
int r = rand();      // Returns a pseudo-random integer between 0 and RAND_MAX.

แก้ไข : บน Linux คุณอาจต้องการใช้สุ่มและ srandom


194
+1 สำหรับความเรียบง่าย แต่มันอาจจะเป็นความคิดที่ดีที่จะเน้นว่า srand () ควรจะเรียกว่าครั้งเดียว นอกจากนี้ในแอปพลิเคชันแบบเธรดคุณอาจต้องการตรวจสอบให้แน่ใจว่าสถานะของตัวสร้างถูกเก็บไว้ต่อเธรดและเริ่มต้นตัวสร้างหนึ่งครั้งสำหรับแต่ละเธรด
RBerteig

41
@trusktr มันซับซ้อน นี่คือเหตุผล: time()เปลี่ยนแปลงเพียงครั้งเดียวต่อวินาที หากคุณทำการเพาะปลูกtime()สำหรับการโทรแต่ละครั้งrand()คุณจะได้รับค่าเดียวกันสำหรับการโทรทุกครั้งในหนึ่งวินาที แต่เหตุผลที่ใหญ่กว่าก็คือคุณสมบัติของrand()และฟังก์ชั่นเช่นนี้เป็นที่รู้จักกันดีที่สุดสำหรับกรณีการใช้งานที่พวกเขาได้รับการเพาะเมล็ดอย่างแน่นอนหนึ่งครั้งต่อการใช้งานและไม่ได้อยู่ในการโทรทุกครั้ง ขึ้นอยู่กับ "การสุ่ม" ด้วยคุณสมบัติที่ยังไม่ทดลองหรือไม่ผ่านการพิสูจน์แล้วทำให้เกิดปัญหา
RBerteig

9
@trusktr สำหรับเครื่องกำเนิดไฟฟ้าเชิงเส้นที่เรียบง่ายเชิงเส้นตรง (ซึ่งก็คือสิ่งrand()ปกติ) การเพาะด้วยวิธีrand()ที่ดีที่สุดจะไม่มีผลเลยและที่แย่ที่สุดจะทำลายคุณสมบัติที่รู้จักของเครื่องกำเนิด นี่เป็นเรื่องที่ลึก เริ่มต้นด้วยการอ่านKnuth เล่มที่ 2บทที่ 3 เกี่ยวกับตัวเลขสุ่มว่าเป็นการแนะนำที่ดีที่สุดสำหรับคณิตศาสตร์และข้อผิดพลาด
RBerteig

21
หลีกเลี่ยงการเตือนคอมไพเลอร์ด้วยการโยน:srand((unsigned int)time(NULL));
GiovaMaster

9
โปรดทราบว่านี่ยังคงเป็นวิธีอ่อนแอในการดู PRNG เมื่อปีที่แล้ว cryptolocker-type virus บน Linux สร้างข้อผิดพลาดในการเริ่มต้นและลดพื้นที่การค้นหาลงอย่างมาก สิ่งที่คุณต้องทำคือรับความคิดที่ดีว่าเมื่อใดที่มีการติดเชื้อแล้วลองใช้เมล็ดพันธุ์ในช่วงเวลานั้น ครั้งสุดท้ายที่ฉันได้ยินแหล่งที่ดีที่สุดของการสุ่มคือ / dev / urandom ซึ่งก็คือสมมุติฐานมาจากแหล่งผสมระหว่างแหล่งข้อมูลที่วุ่นวายเช่นอุณหภูมิในฮาร์ดแวร์ อย่างไรก็ตามหากคุณต้องการให้โปรแกรมของคุณทำงานแตกต่างกันในการวิ่งแต่ละครั้งการแก้ปัญหาข้างต้นก็ใช้ได้
Jemenake

238

rand()ฟังก์ชั่นใน<stdlib.h>ผลตอบแทนที่เป็นจำนวนเต็มสุ่มหลอกระหว่าง 0 RAND_MAXและ คุณสามารถใช้srand(unsigned int seed)เพื่อตั้งค่าเมล็ด

มันเป็นเรื่องธรรมดาที่จะใช้ตัว%ดำเนินการร่วมกับrand()เพื่อให้ได้ช่วงที่แตกต่างกัน (แต่จำไว้ว่าสิ่งนี้ทำให้ความสม่ำเสมอของมันค่อนข้างสม่ำเสมอ) ตัวอย่างเช่น:

/* random int between 0 and 19 */
int r = rand() % 20;

หากคุณจริงๆดูแลเกี่ยวกับความสม่ำเสมอของคุณสามารถทำอะไรเช่นนี้

/* Returns an integer in the range [0, n).
 *
 * Uses rand(), and so is affected-by/affects the same seed.
 */
int randint(int n) {
  if ((n - 1) == RAND_MAX) {
    return rand();
  } else {
    // Supporting larger values for n would requires an even more
    // elaborate implementation that combines multiple calls to rand()
    assert (n <= RAND_MAX)

    // Chop off all of the values that would cause skew...
    int end = RAND_MAX / n; // truncate skew
    assert (end > 0);
    end *= n;

    // ... and ignore results from rand() that fall above that limit.
    // (Worst case the loop condition should succeed 50% of the time,
    // so we can expect to bail out of this loop pretty quickly.)
    int r;
    while ((r = rand()) >= end);

    return r % n;
  }
}

18
มันเป็นเรื่องธรรมดาแต่ก็ไม่ใช่วิธีที่ถูกต้อง ดูนี้และนี้
Lazer

35
@ เลเซอร์: นั่นคือเหตุผลที่ฉันพูดว่า "แม้ว่าจะจำไว้ว่าสิ่งนี้ทำให้ความสม่ำเสมอของมันค่อนข้างจะเหมือนกัน"
Laurence Gonsalves

3
@AbhimanyuAryan The %เป็นตัวดำเนินการโมดูลัส มันให้ส่วนที่เหลือของการหารจำนวนเต็มดังนั้นx % nจะให้ตัวเลขระหว่าง0และn - 1(ตราบเท่าที่xและnทั้งสองเป็นบวก) หากคุณยังพบว่ามีความสับสนลองเขียนโปรแกรมที่มีค่าiตั้งแต่ 0 ถึง 100 และพิมพ์ออกมาi % nตามnจำนวนที่คุณเลือกน้อยกว่า 100
Laurence Gonsalves

2
@ หมอผีฉันไปข้างหน้าและเพิ่มโซลูชันที่เหมือนกันอย่างสมบูรณ์แบบ
Laurence Gonsalves

2
@ รับลิงก์ที่สองที่คุณโพสต์นั้นยังไม่สมบูรณ์เหมือนกัน การหล่อเป็นสองเท่าและไม่ช่วยไม่ได้ ลิงค์แรกที่คุณโพสต์นั้นมีวิธีแก้ปัญหาที่เหมือนกันอย่างสมบูรณ์แม้ว่ามันจะวนซ้ำมากสำหรับขอบเขตเล็ก ๆ ฉันได้เพิ่มโซลูชันที่สมบูรณ์แบบให้กับคำตอบนี้ซึ่งไม่ควรวนซ้ำแม้แต่ในขอบเขตเล็ก ๆ
ลอเรนซ์ Gonsalves

53

หากคุณต้องการอักขระสุ่มหรือจำนวนเต็มที่ปลอดภัย:

ตามที่ได้ระบุไว้ในวิธีสร้างหมายเลขสุ่มในภาษาการเขียนโปรแกรมที่หลากหลายคุณจะต้องทำอย่างใดอย่างหนึ่งต่อไปนี้:

  • ใช้API ของlibsodiumrandombytes
  • นำสิ่งที่คุณต้องการกลับมาใช้ใหม่จากการใช้sysrandom ของ libsodiumด้วยตัวเองอย่างระมัดระวัง
  • กว้างกว่าการใช้งาน/dev/urandom/dev/randomไม่ได้ ไม่ใช่ OpenSSL (หรือ PRSG userspace อื่น ๆ )

ตัวอย่างเช่น:

#include "sodium.h"

int foo()
{
    char myString[32];
    uint32_t myInt;

    if (sodium_init() < 0) {
        /* panic! the library couldn't be initialized, it is not safe to use */
        return 1; 
    }


    /* myString will be an array of 32 random bytes, not null-terminated */        
    randombytes_buf(myString, 32);

    /* myInt will be a random number between 0 and 9 */
    myInt = randombytes_uniform(10);
}

randombytes_uniform() มีความปลอดภัยและไม่มีอคติเข้ารหัสลับ


libsodium RNG ควรทำการ seed ก่อนโทร randombytes_buf หรือไม่
user2199593

เพียงโทรไปsodium_init()ที่จุดใดจุดหนึ่ง ไม่ต้องกังวลกับ RNG มันใช้เคอร์เนล
Scott Arciszewski

หมายเหตุ: ฉันอนุมัติการแก้ไขล่าสุดsodium_init()แม้ว่าจะไม่จำเป็นต้องเป็นส่วนหนึ่งของตัวอย่างเพราะเป็นรายละเอียดที่สำคัญ
Scott Arciszewski

29

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

ดังนั้นเราต้องทำการสุ่มตัวสุ่มด้วยค่าที่เปลี่ยนแปลงอยู่เสมอ เราทำสิ่งนี้โดยป้อนค่าของเวลาปัจจุบันด้วยฟังก์ชัน time ()

ตอนนี้เมื่อเราเรียก rand () หมายเลขสุ่มใหม่จะถูกสร้างขึ้นทุกครั้ง

#include <stdio.h>

int random_number(int min_num, int max_num);

int main(void)
{
    printf("Min : 1 Max : 40 %d\n", random_number(1,40));
    printf("Min : 100 Max : 1000 %d\n",random_number(100,1000));
    return 0;
}

int random_number(int min_num, int max_num)
{
    int result = 0, low_num = 0, hi_num = 0;

    if (min_num < max_num)
    {
        low_num = min_num;
        hi_num = max_num + 1; // include max_num in output
    } else {
        low_num = max_num + 1; // include max_num in output
        hi_num = min_num;
    }

    srand(time(NULL));
    result = (rand() % (hi_num - low_num)) + low_num;
    return result;
}

12
Nice Code แต่ไม่ใช่ความคิดที่ดีที่จะเรียก 'srand (time (NULL));' วิธีการนี้จะสร้างหมายเลขเดียวกันเมื่อมีการเรียกในสำหรับวง
RayOldProf

1
การแก้ไขที่แนะนำที่เกี่ยวข้องกับรหัสมักจะถูกปฏิเสธ มีคนสร้างที่นี่พร้อมกับความคิดเห็น "อัลกอริธึมผิดสามารถสร้างตัวเลขที่ใหญ่กว่าจำนวนสูงสุด" ยังไม่ได้ประเมินการอ้างสิทธิ์ด้วยตนเอง
Martin Smith

1
@ Martin สมิ ธ ปัญหา: 1) ควรจะelse{ low_num=max_num; hi_num=min_num+1;2) hi_num - low_num > INT_MAXล้มเหลวเมื่อ 3) INT_MAX > hi_num - low_num > RAND_MAXค่าละเว้นในสถานการณ์ที่หายาก
chux - Reinstate Monica

1
การลองใหม่เช่นนี้จะทำให้ฟังก์ชันนี้สร้างหมายเลขเดียวกันหากมีการเรียกหลายครั้งในวินาทีเดียวกัน หากคุณต้องการดำเนินการใหม่จริง ๆ ให้ลองใหม่เพียงหนึ่งครั้งต่อวินาที
Élektra

1
เล็กน้อย: hi_num = max_num + 1;ไม่มีการป้องกันการล้น
chux - Reinstate Monica

25

หากคุณต้องการตัวเลขสุ่มที่มีคุณภาพที่ดีขึ้นกว่าสิ่งที่หลอกstdlibให้ตรวจสอบMersenne Twister มันเร็วกว่าด้วย การใช้งานตัวอย่างมีมากมายตัวอย่างเช่นที่นี่


2
+1: ดูเท่ แต่ฉันเพิ่งสร้างเกมทายผล ถ้าฉันจะใช้ตัวสร้างตัวเลขสุ่มในแอปพลิเคชันธุรกิจฉันจะใช้สิ่งนี้อย่างแน่นอน
Kredns

4
อย่าใช้ Mersenne Twister ใช้สิ่งที่ดีเช่น xoroshiro128 + หรือ PCG (ลิงก์ที่เกี่ยวข้อง)
Veedrac

17

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

วิธีที่ดีที่สุดในการสร้างตัวเลขสุ่มใน C คือการใช้ห้องสมุดบุคคลที่สามเช่น OpenSSL ตัวอย่างเช่น,

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/rand.h>

/* Random integer in [0, limit) */
unsigned int random_uint(unsigned int limit) {
    union {
        unsigned int i;
        unsigned char c[sizeof(unsigned int)];
    } u;

    do {
        if (!RAND_bytes(u.c, sizeof(u.c))) {
            fprintf(stderr, "Can't get random bytes!\n");
            exit(1);
        }
    } while (u.i < (-limit % limit)); /* u.i < (2**size % limit) */
    return u.i % limit;
}

/* Random double in [0.0, 1.0) */
double random_double() {
    union {
        uint64_t i;
        unsigned char c[sizeof(uint64_t)];
    } u;

    if (!RAND_bytes(u.c, sizeof(u.c))) {
        fprintf(stderr, "Can't get random bytes!\n");
        exit(1);
    }
    /* 53 bits / 2**53 */
    return (u.i >> 11) * (1.0/9007199254740992.0);
}

int main() {
    printf("Dice: %d\n", (int)(random_uint(6) + 1));
    printf("Double: %f\n", random_double());
    return 0;
}

ทำไมต้องใช้รหัสมาก? ภาษาอื่น ๆ เช่น Java และ Ruby มีฟังก์ชั่นสำหรับจำนวนเต็มหรือลอยแบบสุ่ม OpenSSL ให้การสุ่มแบบไบท์เท่านั้นดังนั้นฉันจึงลองเลียนแบบว่า Java หรือ Ruby จะแปลงมันเป็นจำนวนเต็มหรือลอยตัวได้อย่างไร

สำหรับจำนวนเต็มเราต้องการที่จะหลีกเลี่ยงอคติโมดูโล สมมติว่าเราได้รับจำนวนเต็ม 4 หลักแบบสุ่มrand() % 10000แต่rand()สามารถส่งกลับ 0 ถึง 32767 (เช่นเดียวกับใน Microsoft Windows) แต่ละหมายเลขตั้งแต่ 0 ถึง 2767 จะปรากฏบ่อยกว่าแต่ละหมายเลขจาก 2768 ถึง 9999 หากต้องการลบอคติเราสามารถลองอีกครั้งrand()ในขณะที่ค่าต่ำกว่า 2768 เนื่องจากค่า 30000 จาก 2768 ถึง 32767 แมปไปที่ 10,000 ค่าจาก 0 ถึง 9999

สำหรับโฟลว์เราต้องการ 53 บิตสุ่มเนื่องจากdoubleความแม่นยำถือ 53 บิต (สมมติว่าเป็น IEEE สองเท่า) ถ้าเราใช้มากกว่า 53 บิตเราจะได้อคติการปัดเศษ โปรแกรมเมอร์บางคนเขียนโค้ดเช่นrand() / (double)RAND_MAXแต่rand()อาจส่งคืนเพียง 31 บิตหรือ 15 บิตเท่านั้นใน Windows

RAND_bytes()เมล็ดของ OpenSSL นั้นอาจจะอ่านโดยใช้/dev/urandomLinux หากเราต้องการตัวเลขสุ่มจำนวนมากมันจะช้าเกินกว่าที่จะอ่านพวกเขาทั้งหมด/dev/urandomเพราะพวกเขาจะต้องคัดลอกมาจากเคอร์เนล มันเร็วกว่าที่จะอนุญาตให้ OpenSSL สร้างตัวเลขสุ่มเพิ่มเติมจากเมล็ด

เพิ่มเติมเกี่ยวกับตัวเลขสุ่ม:

  • ของ Perl Perl_seed ()เป็นตัวอย่างของวิธีการคำนวณเมล็ดใน C srand()สำหรับ มันผสมบิตจากเวลาปัจจุบัน ID กระบวนการและพอยน์เตอร์บางตัวหากไม่สามารถอ่าน/dev/urandomได้
  • arc4random_uniform () ของ OpenBSDอธิบายโมดูโลอคติ
  • Java API สำหรับ java.util.Randomอธิบายอัลกอริทึมสำหรับการลบอคติจากจำนวนเต็มแบบสุ่มและการบรรจุ 53 บิตเป็นแบบสุ่มลอย

ขอบคุณสำหรับคำตอบเพิ่มเติมนี้ โปรดทราบว่าจากคำตอบปัจจุบัน 24 คำตอบสำหรับคำถามนี้คุณเป็นคนเดียวที่มีการตีความเพิ่มเติมเพื่อจัดการกับfloat/ doubleดังนั้นฉันได้ชี้แจงคำถามที่ยึดติดกับintตัวเลขเพื่อหลีกเลี่ยงการทำให้กว้างเกินไป มีคำถาม C อื่น ๆ ที่เกี่ยวข้องเฉพาะกับการมีfloat/ doubleค่าสุ่มดังนั้นคุณอาจต้องการที่จะ repost ครึ่งหลังของคุณคำตอบของคำถามเช่นstackoverflow.com/questions/13408990/...
Cœur

11

หากระบบของคุณรองรับarc4randomตระกูลของฟังก์ชั่นฉันขอแนะนำให้ใช้ฟังก์ชันเหล่านั้นแทนrandฟังก์ชั่นมาตรฐาน

arc4randomครอบครัวรวมถึง:

uint32_t arc4random(void)
void arc4random_buf(void *buf, size_t bytes)
uint32_t arc4random_uniform(uint32_t limit)
void arc4random_stir(void)
void arc4random_addrandom(unsigned char *dat, int datlen)

arc4random ส่งคืนจำนวนเต็มที่ไม่ได้ลงชื่อแบบ 32 บิตแบบสุ่ม

arc4random_bufใส่เนื้อหาแบบสุ่มในพารามิเตอร์buf : void *มัน จำนวนเนื้อหาถูกกำหนดโดยbytes : size_tพารามิเตอร์

arc4random_uniformส่งคืนเลขจำนวนเต็มแบบไม่ระบุชื่อแบบ 32 บิตแบบสุ่มซึ่งเป็นไปตามกฎ: 0 <= arc4random_uniform(limit) < limitโดยขีด จำกัด จะเป็นจำนวนเต็มแบบ 32 บิตแบบไม่ได้ลงนาม

arc4random_stirอ่านข้อมูลจาก/dev/urandomและส่งผ่านข้อมูลไปarc4random_addrandomยังเพื่อสุ่มเพิ่มเติมเป็นกลุ่มตัวเลขสุ่มภายใน

arc4random_addrandomจะถูกใช้โดยarc4random_stirการเติมมันเป็นกลุ่มหมายเลขสุ่มภายในตามข้อมูลที่ส่งผ่านไป

หากคุณไม่มีฟังก์ชั่นเหล่านี้ แต่คุณใช้ระบบปฏิบัติการยูนิกซ์คุณสามารถใช้รหัสนี้ได้:

/* This is C, not C++ */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h> /* exit */
#include <stdio.h> /* printf */

int urandom_fd = -2;

void urandom_init() {
  urandom_fd = open("/dev/urandom", O_RDONLY);

  if (urandom_fd == -1) {
    int errsv = urandom_fd;
    printf("Error opening [/dev/urandom]: %i\n", errsv);
    exit(1);
  }
}

unsigned long urandom() {
  unsigned long buf_impl;
  unsigned long *buf = &buf_impl;

  if (urandom_fd == -2) {
    urandom_init();
  }

  /* Read 4 bytes, or 32 bits into *buf, which points to buf_impl */
  read(urandom_fd, buf, sizeof(long));
  return buf_impl;
}

urandom_initฟังก์ชั่นเปิดอุปกรณ์และทำให้อธิบายไฟล์ใน/dev/urandomurandom_fd

urandomฟังก์ชั่นเป็นพื้นเดียวกับการเรียกrandยกเว้นความปลอดภัยมากขึ้นและจะส่งกลับlong(อาจมีการเปลี่ยนแปลงได้อย่างง่ายดาย)

อย่างไรก็ตาม/dev/urandomอาจช้าไปหน่อยขอแนะนำให้คุณใช้เป็นเมล็ดสำหรับเครื่องกำเนิดหมายเลขสุ่มแบบอื่น

หากระบบของคุณไม่ได้มี/dev/urandomแต่ไม่มี/dev/randomหรือแฟ้มที่คล้ายกันแล้วคุณก็สามารถเปลี่ยนเส้นทางผ่านไปในopen urandom_initการโทรและ API ที่ใช้ในurandom_initและurandomเป็น (ฉันเชื่อว่า) เป็นไปตาม POSIX และเป็นเช่นนี้ควรทำงานได้มากที่สุดถ้าไม่ใช่ระบบที่สอดคล้องกับ POSIX ทั้งหมด

หมายเหตุ: การอ่านจาก/dev/urandomจะไม่ปิดกั้นหากมีเอนโทรปีไม่เพียงพอดังนั้นค่าที่สร้างขึ้นภายใต้สถานการณ์เช่นนั้นอาจไม่ปลอดภัยแบบเข้ารหัส หากคุณกังวลเกี่ยวกับสิ่งนั้นให้ใช้/dev/randomซึ่งจะปิดกั้นเสมอหากมีเอนโทรปีไม่เพียงพอ

หากคุณอยู่ในระบบอื่น (เช่น Windows) ให้ใช้randหรือ API พกพาที่ไม่ขึ้นอยู่กับแพลตฟอร์มเฉพาะของ Windows ภายใน

ฟังก์ชั่นสำหรับ Wrapper urandom, randหรือarc4randomโทร:

#define RAND_IMPL /* urandom(see large code block) | rand | arc4random */

int myRandom(int bottom, int top){
    return (RAND_IMPL() % (top - bottom)) + bottom;
}

8

STL ไม่อยู่สำหรับซีคุณต้องโทรหรือดีกว่ายังrand เหล่านี้มีการประกาศในส่วนหัวห้องสมุดมาตรฐานrandom คือ POSIX เป็นฟังก์ชันจำเพาะของ BSDstdlib.hrandrandom

ความแตกต่างระหว่างrandกับrandomคือrandomส่งคืนตัวเลขสุ่มแบบ 32 บิตที่สามารถใช้งานได้มากกว่าrandโดยทั่วไปแล้วจะส่งกลับตัวเลข 16 บิต BSD manpages แสดงให้เห็นว่าบิตที่ต่ำกว่าrandนั้นมีลักษณะเป็นวงรอบและสามารถคาดการณ์ได้ดังนั้นจึงrandไม่มีประโยชน์สำหรับตัวเลขขนาดเล็ก


3
@ Neil - เนื่องจากคำตอบทั้งหมดพูดถึง STL ฉันสงสัยว่าคำถามนั้นได้รับการแก้ไขอย่างรวดเร็วเพื่อลบการอ้างอิงที่จำเป็น
ไมเคิลเสี้ยน

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

@Chris คุณสามารถถ้าขนาดของตัวเลขสุ่มเป็นที่รู้จัก แต่ถ้าขนาดที่ต้องการของตัวเลขสุ่มเปลี่ยนแปลงระหว่างรันไทม์ (เช่นสับอาร์เรย์แบบไดนามิก ฯลฯ ) มันจะยากที่จะแก้ไขข้อแม้เช่นนี้
dreamlax

ฉันไม่พบฟังก์ชันแบบสุ่มใด ๆที่นี่ :-(
kasia.b

@ kasia.b ในการเชื่อมโยงที่มีและextern int rand(void); extern void srand(unsigned int);
RastaJedi

7

ดูที่ISAAC (การเปลี่ยนทิศทางเปลี่ยนการสะสมเพิ่มและนับ) มันกระจายอย่างสม่ำเสมอและมีความยาววงจรเฉลี่ย 2 ^ 8295


2
ISAAC เป็น RNG ที่น่าสนใจเนื่องจากความเร็ว แต่ยังไม่ได้รับความสนใจการเข้ารหัสที่จริงจัง
user2398029

4

นี่เป็นวิธีที่ดีในการรับตัวเลขสุ่มระหว่างตัวเลขสองตัวที่คุณเลือก

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

    #define randnum(min, max) \
        ((rand() % (int)(((max) + 1) - (min))) + (min))

int main()
{
    srand(time(NULL));

    printf("%d\n", randnum(1, 70));
}

เอาต์พุตในครั้งแรก: 39

เอาท์พุทเป็นครั้งที่สอง: 61

เอาท์พุทเป็นครั้งที่สาม: 65

คุณสามารถเปลี่ยนค่าหลังจากrandnumเป็นตัวเลขใดก็ได้ที่คุณเลือกและจะสร้างตัวเลขสุ่มสำหรับคุณระหว่างตัวเลขทั้งสอง


4

rand()คุณต้องการที่จะใช้ หมายเหตุ ( สำคัญมาก ): ตรวจสอบให้แน่ใจว่าได้ตั้งค่า seed สำหรับฟังก์ชัน rand ถ้าคุณทำไม่ได้ตัวเลขสุ่มของคุณจะไม่สุ่มอย่างแท้จริง สิ่งนี้สำคัญมากและสำคัญมาก โชคดีที่คุณมักจะสามารถใช้การรวมกันของระบบติ๊กตัวจับเวลาและวันที่เพื่อรับเมล็ดพันธุ์ที่ดี


6
สองคะแนน a) ตัวเลขสุ่มของคุณไม่ใช่การสุ่ม "อย่างแท้จริง" ไม่ว่าคุณจะสร้างเครื่องกำเนิดไฟฟ้าแบบใด และ b) มันสะดวกมากที่จะให้ลำดับการสุ่มหลอกเหมือนกันเสมอในหลาย ๆ สถานการณ์ - สำหรับการทดสอบ

16
หากสำคัญมากที่หมายเลขของคุณจะสุ่มอย่างแท้จริงคุณไม่ควรใช้ฟังก์ชัน rand ()
tylerl

1
ค่าจากแรนด์ไม่ได้สุ่มอย่างแท้จริงไม่ว่าคุณจะตั้งค่าเมล็ดหรือไม่ก็ตาม รับเมล็ดที่รู้จักลำดับสามารถคาดเดาได้ การสร้างตัวเลขสุ่ม "แท้จริง" นั้นยาก ไม่มีเอนโทรปีเกี่ยวข้องกับแรนด์
dreamlax

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

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

4

FWIW คำตอบคือใช่มีstdlib.hฟังก์ชั่นที่เรียกว่าrand; ฟังก์ชั่นนี้ได้รับการปรับจูนเป็นหลักเพื่อความเร็วและการกระจายไม่ใช่สำหรับการคาดเดาไม่ได้ ฟังก์ชันสุ่มในตัวเกือบทั้งหมดสำหรับภาษาและกรอบต่าง ๆ ใช้ฟังก์ชันนี้ตามค่าเริ่มต้น นอกจากนี้ยังมีเครื่องกำเนิดเลขสุ่ม "เข้ารหัส" ที่คาดการณ์ได้น้อยกว่ามาก แต่ทำงานได้ช้ากว่ามาก ควรใช้สิ่งเหล่านี้ในแอปพลิเคชันที่เกี่ยวข้องกับความปลอดภัยทุกประเภท


4

หวังว่านี่จะสุ่มขึ้นมามากกว่าการใช้เพียงsrand(time(NULL))เล็กน้อย

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    srand((unsigned int)**main + (unsigned int)&argc + (unsigned int)time(NULL));
    srand(rand());

    for (int i = 0; i < 10; i++)
        printf("%d\n", rand());
}

1
เพิ่ม srand (rand ()) จะไม่เพิ่มการสุ่มของลำดับหากโปรแกรมนี้ทำงานหลาย ๆ ครั้งภายใน 1 วินาที เวลา (NULL) จะยังคงส่งคืนค่าเดิมสำหรับแต่ละรายการแรนด์แรก () จะส่งคืนความยาวเท่ากันและการเรียกที่สองไปยัง srand () จะเป็นค่าเดียวกันทำให้มีลำดับสุ่มแบบเดียวกัน การใช้ที่อยู่ของ argc อาจช่วยได้ก็ต่อเมื่อมีการรับรองว่าที่อยู่นี้จะแตกต่างกันในทุกการทำงานของโปรแกรมซึ่งไม่เป็นความจริงเสมอไป
theferrit32

3

STL คือ C ++ ไม่ใช่ C ดังนั้นฉันไม่รู้ว่าคุณต้องการอะไร หากคุณต้องการ C อย่างไรก็ตามมีrand()และsrand()ฟังก์ชั่น:

int rand(void);

void srand(unsigned seed);

เหล่านี้เป็นส่วนหนึ่งของ ANSI C นอกจากนี้ยังมีrandom()ฟังก์ชั่น:

long random(void);

แต่เท่าที่ฉันสามารถบอกได้ว่าrandom()ไม่ใช่มาตรฐาน ANSI C ห้องสมุดบุคคลที่สามอาจไม่ใช่ความคิดที่ไม่ดี แต่ทั้งหมดขึ้นอยู่กับว่าคุณต้องการสร้างจำนวนสุ่มเพียงใด


3

C โปรแกรมเพื่อสร้างตัวเลขสุ่มระหว่าง 9 และ 50

#include <time.h>
#include <stdlib.h>

int main()
{
    srand(time(NULL));
    int lowerLimit = 10, upperLimit = 50;
    int r =  lowerLimit + rand() % (upperLimit - lowerLimit);
    printf("%d", r);
}

โดยทั่วไปเราสามารถสร้างตัวเลขสุ่มระหว่างlowerLimit และ upperLimit-1

ie lowerLimit ถูกรวมหรือว่าr ∈ [lowerLimit, UpperLimit)


@Pang นั่นคือสิ่งที่ฉันพูดถึงอย่างชัดเจนระหว่าง 9 และ 50 ไม่ใช่จาก 9 และ 50
Shivam K. Thakkar

2
การทำงานแบบโมดูโลของคุณมีอคติ
jww

2

rand() เป็นวิธีที่สะดวกที่สุดในการสร้างตัวเลขสุ่ม

นอกจากนี้คุณยังสามารถจับหมายเลขสุ่มจากบริการออนไลน์ใด ๆ เช่น random.org


1
นอกจากนี้คุณยังสามารถจับหมายเลขสุ่มจากบริการออนไลน์ใด ๆ เช่น random.org Bounty หากคุณมีวิธีพกพาที่มีประสิทธิภาพใน C
MD XF

2
#include <stdio.h>
#include <stdlib.h>

void main() 
{
    int visited[100];
    int randValue, a, b, vindex = 0;

    randValue = (rand() % 100) + 1;

    while (vindex < 100) {
        for (b = 0; b < vindex; b++) {
            if (visited[b] == randValue) {
                randValue = (rand() % 100) + 1;
                b = 0;
            }
        }

        visited[vindex++] = randValue;
    }

    for (a = 0; a < 100; a++)
        printf("%d ", visited[a]);
}

จากนั้นให้นิยามที่ด้านบนสุดด้วยตัวแปร @ b4hand
Muhammad Sadiq

ในขณะที่ฉันแสดงความคิดเห็นนั้นฉันไม่ได้รับอนุญาตให้แก้ไขแบบสากลและโดยทั่วไปการเปลี่ยนแปลงรหัสที่เปลี่ยนคำตอบที่แท้จริงจะได้รับการปฏิเสธ หากคุณไม่สนใจที่จะแก้ไขคำตอบของคุณฉันสามารถทำได้
b4hand

หากความตั้งใจของคุณคือการสร้างการเรียงสับเปลี่ยนของค่าที่ไม่ซ้ำกันรหัสนี้ยังคงมีข้อบกพร่อง มันสร้างค่า 84 สองครั้งและไม่สร้างค่า 48 นอกจากนี้มันไม่ได้เมล็ดตัวสร้างตัวเลขสุ่มดังนั้นลำดับจะเหมือนกันในทุกการดำเนินการ
b4hand

1
ตัวแปร a และ b ถูกกำหนดไว้ในรหัสนี้ .. มันเป็นข้อผิดพลาดฟรี .. ไม่มีไวยากรณ์และข้อผิดพลาดเชิงตรรกะด้วย
Muhammad Sadiq

ไม่ถูกต้อง ฉันยืนยันการส่งออกแล้ว
b4hand

1
#include <stdio.h>
#include <dos.h>

int random(int range);

int main(void)
{
    printf("%d", random(10));
    return 0;
}

int random(int range)
{
    struct time t;
    int r;

    gettime(&t);
    r = t.ti_sec % range;
    return r;
}

1

ในซีพียู x86_64 ที่ทันสมัยคุณสามารถใช้ตัวสร้างหมายเลขสุ่มฮาร์ดแวร์ได้ _rdrand64_step()

รหัสตัวอย่าง:

#include <immintrin.h>

uint64_t randVal;
if(!_rdrand64_step(&randVal)) {
  // Report an error here: random number generation has failed!
}
// If no error occured, randVal contains a random 64-bit number

1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

//generate number in range [min,max)
int random(int min, int max){
    int number = min + rand() % (max - min);
    return number; 
}

//Driver code
int main(){
    srand(time(NULL));
    for(int i = 1; i <= 10; i++){
        printf("%d\t", random(10, 100));
    }
    return 0;
}

0

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

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char *argv[])
{
    int i;
    int dice[6];

    for (i = 0; i < 6; i++) 
      dice[i] = 0;
    srand(time(NULL));

    const int TOTAL = 10000000;
    for (i = 0; i < TOTAL; i++)
      dice[(rand() % 6)] += 1;

    double pers = 0.0, tpers = 0.0;
    for (i = 0; i < 6; i++) {
      pers = (dice[i] * 100.0) / TOTAL;
      printf("\t%1d  %5.2f%%\n", dice[i], pers);
      tpers += pers;
    }
    printf("\ttotal:  %6.2f%%\n", tpers);
}

และนี่คือผลลัพธ์:

 $ gcc -o t3 t3.c
 $ ./t3 
        1666598  16.67%     
        1668630  16.69%
        1667682  16.68%
        1666049  16.66%
        1665948  16.66%
        1665093  16.65%
        total:  100.00%
 $ ./t3     
        1667634  16.68%
        1665914  16.66%
        1665542  16.66%
        1667828  16.68%
        1663649  16.64%
        1669433  16.69%
        total:  100.00%

ฉันไม่ทราบว่าคุณต้องการหมายเลขสุ่มของคุณอย่างไร

แก้ไข: มันจะเป็นความคิดที่ดีในการเริ่มต้น PRNG time(NULL)กับสิ่งที่ดีกว่า


แรนด์ () สามารถล้มเหลวในการทดสอบการสุ่มอื่น ๆ เช่นการทดสอบมิจฉาทิฐิ Rand () แตกต่างจากแพลตฟอร์มหนึ่งไปอีกแพลตฟอร์ม; ค่า rand () จาก GNU / Linux อาจดีกว่าค่าจาก BSD หรือ Windows
George Koehler

นี่ไม่ใช่วิธีที่ถูกต้องในการทดสอบการสุ่ม
Veedrac

ขึ้นอยู่กับวัตถุประสงค์และรูปแบบการคุกคาม / ความเสี่ยง สำหรับ RNG ที่มีการเข้ารหัสลับอย่างแน่นอนใช้ RDRAND (หรือ RDSEED) สำหรับนักโยนลูกเต๋าแบบง่าย ๆ (ไม่ใช่ระดับคาสิโน) IMHO ด้านบนควรเพียงพอ คำหลักคือ "ดีพอ "
หนู

0

ฉันมีปัญหาร้ายแรงกับตัวสร้างตัวเลขสุ่มหลอกในแอปพลิเคชันล่าสุดของฉัน: ฉันเรียกโปรแกรม C ของฉันซ้ำ ๆ ผ่านสคริปต์ pyhton และฉันใช้เป็นรหัสต่อไปนี้:

srand(time(NULL))

อย่างไรก็ตามเนื่องจาก:

  • แรนด์จะสร้างลำดับการสุ่มหลอกเดียวกันให้เมล็ดเดียวกันใน srand (ดู man srand );
  • ตามที่ระบุไว้แล้วฟังก์ชันเวลาเปลี่ยนเพียงวินาทีจากวินาที: หากแอปพลิเคชันของคุณทำงานหลาย ๆ ครั้งภายในวินาทีเดียวกันtimeจะส่งคืนค่าเดิมทุกครั้ง

โปรแกรมของฉันสร้างลำดับหมายเลขเดียวกัน คุณสามารถทำ 3 สิ่งเพื่อแก้ไขปัญหานี้:

  1. แสดงเวลาผสมกับข้อมูลอื่น ๆ ที่เปลี่ยนแปลงเมื่อทำงาน (ในแอปพลิเคชันของฉันชื่อเอาต์พุต):

    srand(time(NULL) | getHashOfString(outputName))

    ฉันใช้djb2เป็นฟังก์ชั่นแฮช

  2. เพิ่มความละเอียดของเวลา บนแพลตฟอร์มของฉันclock_gettimeพร้อมใช้งานดังนั้นฉันจึงใช้มัน:

    #include<time.h>
    struct timespec nanos;
    clock_gettime(CLOCK_MONOTONIC, &nanos)
    srand(nanos.tv_nsec);
  3. ใช้ทั้งสองวิธีร่วมกัน:

    #include<time.h>
    struct timespec nanos;
    clock_gettime(CLOCK_MONOTONIC, &nanos)
    srand(nanos.tv_nsec | getHashOfString(outputName));

ตัวเลือกที่ 3 ทำให้คุณมั่นใจได้ (เท่าที่ฉันรู้) การสุ่มเมล็ดที่ดีที่สุด แต่มันอาจสร้างความแตกต่างในแอปพลิเคชันที่รวดเร็วมากเท่านั้น ในความคิดของฉันตัวเลือกที่ 2 เป็นการเดิมพันที่ปลอดภัย


แม้ว่าจะมีฮิวริสติกเหล่านี้ แต่อย่าพึ่งพา rand () สำหรับข้อมูลการเข้ารหัส
domenukk

rand()ไม่ควรใช้สำหรับข้อมูลการเข้ารหัสลับฉันเห็นด้วย อย่างน้อยสำหรับฉันแอปพลิเคชันของฉันไม่เกี่ยวข้องกับข้อมูลการเข้ารหัสดังนั้นสำหรับฉันมันก็โอเคกับวิธีการที่กำหนด
Koldar

0

แม้จะมีคำแนะนำของทุกคนrand()ที่นี่คุณไม่ต้องการใช้rand()เว้นแต่คุณจะต้อง! ตัวเลขสุ่มที่rand()สร้างมักจะไม่ดีมาก อ้างจากหน้าลินุกซ์:

เวอร์ชันของrand()และsrand()ใน Linux C Library ใช้ตัวสร้างตัวเลขสุ่มแบบเดียวกับrandom(3)และsrandom(3)ดังนั้นบิตลำดับที่ต่ำกว่าควรเป็นแบบสุ่มเป็นบิตลำดับสูงกว่า อย่างไรก็ตามในการใช้งาน rand () ที่เก่ากว่าและในการใช้งานในปัจจุบันในระบบที่แตกต่างกันบิตลำดับที่ต่ำกว่านั้นจะสุ่มน้อยกว่าบิตลำดับสูงกว่ามาก อย่าใช้ฟังก์ชั่นนี้ในแอพพลิเคชั่นที่ต้องการพกพาเมื่อจำเป็นต้องใช้การสุ่มตัวอย่างที่ดี ( ใช้random(3)แทน )

เกี่ยวกับการพกพาrandom()ยังถูกกำหนดโดยมาตรฐาน POSIX ในบางเวลา rand()เก่ากว่ามันปรากฏในสเป็ค POSIX.1 แรก (IEEE Std 1003.1-1988) แล้วrandom()ปรากฏครั้งแรกในแต่มาตรฐาน POSIX ปัจจุบันเป็น POSIX.1-2008 แล้ว (IEEE Std 1003.1-2008) ซึ่งได้รับการอัปเดตเมื่อหนึ่งปีก่อน (IEEE Std 1003.1-2008, 2016 Edition) ดังนั้นฉันจะพิจารณาrandom()ให้เป็นแบบพกพามาก

POSIX.1-2001 ยังแนะนำlrand48()และmrand48()ฟังก์ชั่นดูที่นี่ :

ตระกูลของฟังก์ชั่นนี้จะสร้างตัวเลขสุ่มหลอกโดยใช้อัลกอริธึมเชิงเส้นตรงและคณิตศาสตร์เลขจำนวนเต็ม 48 บิต

และแหล่งสุ่มหลอกที่ดีก็คือarc4random()ฟังก์ชั่นที่มีอยู่ในหลาย ๆ ระบบ ไม่ได้เป็นส่วนหนึ่งของมาตรฐานอย่างเป็นทางการที่ปรากฏใน BSD ประมาณปี 1997 แต่คุณสามารถค้นหาได้บนระบบเช่น Linux และ macOS / iOS


random()ไม่มีอยู่ใน Windows
Björn Lindqvist

@ BjörnLindqvist Windows ยังไม่มีระบบ POSIX; มันค่อนข้างเป็นระบบเดียวในตลาดที่ไม่รองรับ POSIX APIs อย่างน้อย (ซึ่งแม้แต่ระบบที่ถูกล็อคเช่น iOS ก็สนับสนุน) Windows รองรับเฉพาะrand()เมื่อจำเป็นตามมาตรฐาน C เท่านั้น สำหรับสิ่งอื่นคุณต้องการโซลูชันพิเศษสำหรับ Windows เท่านั้นเช่นเคย #ifdef _WIN32เป็นวลีที่คุณจะเห็นบ่อยที่สุดในรหัสข้ามแพลตฟอร์มที่ต้องการสนับสนุน Windows รวมถึงมักจะมีวิธีแก้ไขปัญหาเดียวที่ทำงานได้กับทุกระบบและเป็นสิ่งที่จำเป็นสำหรับ Windows เท่านั้น
Mecki

0

สำหรับแอปพลิเคชัน Linux C:

นี่คือรหัสทำใหม่ของฉันจากคำตอบข้างต้นซึ่งเป็นไปตามแนวทางปฏิบัติของรหัส C ของฉันและส่งกลับบัฟเฟอร์แบบสุ่มทุกขนาด (พร้อมรหัสส่งคืนที่เหมาะสม ฯลฯ ) ให้แน่ใจว่าได้โทรurandom_open()หนึ่งครั้งที่จุดเริ่มต้นของโปรแกรมของคุณ

int gUrandomFd = -1;

int urandom_open(void)
{
    if (gUrandomFd == -1) {
        gUrandomFd = open("/dev/urandom", O_RDONLY);
    }

    if (gUrandomFd == -1) {
        fprintf(stderr, "Error opening /dev/urandom: errno [%d], strerrer [%s]\n",
                  errno, strerror(errno));
        return -1;
    } else {
        return 0;
    }
}


void urandom_close(void)
{
    close(gUrandomFd);
    gUrandomFd = -1;
}


//
// This link essentially validates the merits of /dev/urandom:
// http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
//
int getRandomBuffer(uint8_t *buf, int size)
{
    int ret = 0; // Return value

    if (gUrandomFd == -1) {
        fprintf(stderr, "Urandom (/dev/urandom) file not open\n");
        return -1;
    }

    ret = read(gUrandomFd, buf, size);

    if (ret != size) {
        fprintf(stderr, "Only read [%d] bytes, expected [%d]\n",
                 ret, size);
        return -1;
    } else {
        return 0;
    }
}

-2

[min, max)วิธีการแก้ปัญหาที่เรียบง่ายของฉันควรจะทำงานสำหรับตัวเลขสุ่มในช่วง ใช้srand(time(NULL))ก่อนเรียกใช้ฟังก์ชัน

int range_rand(int min_num, int max_num) {
    if (min_num >= max_num) {
        fprintf(stderr, "min_num is greater or equal than max_num!\n"); 
    }
    return min_num + (rand() % (max_num - min_num));
} 

-3

ลองสิ่งนี้ฉันรวบรวมมันจากแนวคิดบางอย่างที่อ้างอิงข้างต้น:

/*    
Uses the srand() function to seed the random number generator based on time value,
then returns an integer in the range 1 to max. Call this with random(n) where n is an integer, and you get an integer as a return value.
 */

int random(int max) {
    srand((unsigned) time(NULL));
    return (rand() % max) + 1;
}

16
รหัสนี้ไม่ดี การโทรsrand()ทุกครั้งที่คุณต้องการโทรหาrand()เป็นความคิดที่แย่มาก เนื่องจากtime()โดยทั่วไปแล้วจะส่งคืนค่าในไม่กี่วินาทีที่เรียกใช้ฟังก์ชันนี้อย่างรวดเร็วจะส่งกลับค่า "สุ่ม" ที่เหมือนกัน
Blastfurnace

3
ฟังก์ชั่นนี้จะสับสนกับrandom()หน้าที่ของ Unix
George Koehler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.