Как сгенерировать случайное целое число из диапазона
Это следует из ранее опубликованного вопроса:
как сгенерировать случайное число в C?
Я хочу иметь возможность генерировать случайное число из определенного диапазона, например от 1 до 6, чтобы имитировать стороны штампа.
Как бы я это сделал?
10 ответов:
все ответы до сих пор математически неправильно. Возвращение
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()поскольку он имеет лучшее распределение (как отмечалось на главной странице для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), включая отрицательные значения.
следуя ответу @Ryan Reich, я подумал, что предложу свою очищенную версию. Первая проверка границ не требуется, учитывая вторую проверку границ, и я сделал ее итеративной, а не рекурсивной. Он возвращает значения в диапазоне [min, max], где
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); }
unsigned int randr(unsigned int min, unsigned int max) { double scaled = (double)rand()/RAND_MAX; return (max - min +1)*scaled + min; }посмотреть здесь другие варианты.
вот формула если вы знаете максимальное и минимальное значения диапазона, и вы хотите генерировать числа включительно в диапазоне:
r = (rand() % (max + 1 - min)) + min
не могли бы вы просто сделать:
srand(time(NULL)); int r = ( rand() % 6 ) + 1;
%оператор модуля. По сути, он просто разделится на 6 и вернет остаток... от 0 - 5
для тех, кто понимает проблему смещения, но не может выдержать непредсказуемое время выполнения методов, основанных на отказе, эта серия создает постепенно менее смещенное случайное целое число в
[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); ...он делает это путем синтеза высокоточного фиксированного случайного числа
i * log_2(RAND_MAX + 1)биты (гдеi- это количество итераций) и выполнение длинного умножения наn.когда количество битов достаточно велико по сравнению с
n, смещение становится неизмеримо малым.это не имеет значения, если
RAND_MAX + 1меньшеn(например,этот вопрос), или если это не сила двух, но необходимо позаботиться о том, чтобы избежать переполнения целого числа, еслиRAND_MAX * nбольшой.
чтобы избежать смещения по модулю (предложенного в других ответах), вы всегда можете использовать:
arc4random_uniform(MAX-MIN)+MINгде " MAX "- верхняя граница, а" MIN " - нижняя граница. Например, для чисел от 10 до 20:
arc4random_uniform(20-10)+10 arc4random_uniform(10)+10простое решение и лучше, чем с помощью "rand() % N".
вот немного более простой алгоритм, чем решение Райана Райха:
/// 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
хотя Райан прав, решение может быть гораздо проще на основе того, что известно об источнике случайности. Чтобы переформулировать проблему:
- есть источник случайности, выводящий целые числа в диапазоне
[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 и пример кода, который производит 64-бит (без знака) случайных чисел.
int rdrand64_step(unsigned long long int *therand) { unsigned long long int foo; int cf_error_status; asm("rdrand %%rax; \ mov ,%%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 не поддерживает эти новые инструкции).
как было сказано ранее, по модулю недостаточно, потому что он искажает распределение. Вот мой код, который маскирует биты и использует их, чтобы гарантировать, что распределение не искажено.
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]); } }
Comments