Как заполнить файл случайными числами c

Обновлено: 16.05.2024

Пожалуйста, приостановите работу AdBlock на этом сайте.

Иногда может возникнуть необходимость в генерации случайных чисел. Простой пример.

Пример: Определение победителя в конкурсе репостов.

Имеется список из 53 человек. Необходимо выбрать из них победителя. Если вы выберете его самостоятельно, то вас могут обвинить в предвзятости. Поэтому вы решили написать программу. Она будет работать следующим образом. Вы вводите количество участников N , после чего программа выводит одно число – номер победителя.

Как получить число от игрока, вам уже известно. А вот как заставить компьютер загадать случайное число? В этом уроке вы этому научитесь.

Функция rand().

Данная функция возвращает случайное целое число в диапазоне от нуля до RAND_MAX . RAND_MAX это специальная константа языка Си, в которой содержится максимальное целое число, которое может быть возвращено функцией rand() .

Функция rand() определена в заголовочном файле stdlib.h . Поэтому, если хотите использовать rand в своей программе, не забудьте подключить этот заголовочный файл. Константа RAND_MAX тоже определена в этом файле. Вы можете найти этот файл у себя на компьютере и посмотреть её значение.

Давайте посмотрим на эту функцию в действии. Запустим следующий код:

Должно получиться что-то вроде этого.

Пять случайных чисел, сгенерированных функцийе rand

Рис.1 Пять случайных чисел, сгенерированных функцийе rand

Но нам бы хотелось получить числа от 1 до 53 , а не всё подряд. Ниже описано несколько трюков, позволяющих наложить ограничения на функцию rand() .

Ограничить случайные числа сверху.

Кто в школе ждал момента, когда ему пригодится математика, приготовьтесь. Этот момент наступил. Чтобы ограничить сверху случайные числа, можно воспользоваться операцией получения остатка от деления, которую вы изучили в прошлом уроке. Наверное вы знаете, что остаток от деления на числа K всегда меньше числа K . Например, при делении на 4 могут получиться остатки 0, 1, 2 и 3 . Поэтому если вы хотите ограничить сверху случайные числа числом K , то просто возьмите остаток от деления на K . Вот так:

Пять случайных чисел меньше 100

Рис.2 Пять случайных чисел меньше 100

Ограничить числа снизу.

Функция rand возвращает случайные числа из отрезка [0, RAND_MAX] . А что если нам нужны только числа большие числа M (например, 1000 )? Как быть? Всё просто. Просто прибавим к тому, что вернула функция rand, наше значение M . Тогда если функция вернёт 0 , итоговый ответ будет M , если 2394 , то итоговый ответ будет M + 2394 . Этим действием мы как бы сдвигаем все числа на M единиц вперёд.

Задать границы функции rand сверху и снизу.

Например, получить числа от 80 до 100 . Кажется, нужно просто объединить два способа, которые приведены выше. Получим что-то вроде этого:

Попробуйте запустить эту программу. Удивлены?

Да, такой способ работать не будет. Давайте прокрутим эту программу руками, чтобы убедиться в том, что мы допустили ошибку. Допустим rand() вернула число 143 . Остаток от деления на 100 равен 43 . Дальше 80 + 43 = 123 . Значит такой способ не работает. Подобная конструкция выдаст числа от 80 до 179 .

Давайте разберём по действиям наше выражение. rand()%100 может выдать числа от 0 до 99 включительно. Т.е. из отрезка [0; 99] .
Операция + 80 сдвигает наш отрезок на 80 единиц вправо. Получаем [80; 179] .
Как видим, проблема у нас заключается в правой границе отрезка, она сдвинута вправо на 79 единиц. Это наше исходное число 80 минус 1 . Давайте наведём порядок и сдвинем правую границу назад: 80 + rand()%(100 - 80 + 1) . Тогда всё должно сработать как надо.

В общем случае если нам нужно получить числа из отрезка [A;B] , то необходимо воспользоваться следующей конструкцией:
A + rand()%(B-A+1) .

Согласно этой формуле перепишем нашу последнюю программу:

Случайные числа из диапазона [80;100]

Рис.3 Случайные числа из диапазона [80;100]

Ну вот, теперь вы можете решить исходную задачу урока. Сгенерировать число из отрезка [1; N] . Или не можете?

Но прежде ещё немного полезной информации. Запустите последнюю программу три раза подряд и записывайте себе случайные числа, которые она генерирует. Заметили?

Функция srand().

Да, каждый раз появляются одни и те же одинаковые числа. «Так себе генератор!» – скажете вы. И будете не совсем правы. Действительно, генерируются всё время одинаковые числа. Но мы можем на это повлиять, для этого используется функция srand() , которая также определена в заголовочном файле stdlib.h . Она инициализирует генератор случайных чисел начальным числом.

Скомпилируйте и запустите несколько раз вот эту программу:

Теперь поменяйте аргумент функции srand() на другое число (надеюсь вы ещё не забыли, что такое аргумент функции?) и снова скомпилируйте и запустите программу. Последовательность чисел должна измениться. Как только мы меняем аргумент в функции srand – меняется и последовательность. Не очень практично, не правда ли? Чтобы изменить последовательность, нужно перекомпилировать программу. Вот бы это число туда подставлялось автоматически.

И это можно сделать. Например, воспользуемся функцией time() , которая определена в заголовочном файле time.h . Данная функция, если ей в качестве аргумента передать NULL , возвращает количество секунд, прошедших c 1 января 1970 года . Вот посмотрите, как это делается.

Вы спросите, а что такое NULL ? Резонный вопрос. А я вам пока отвечу, что это специальное зарезервированное слово такое. Могу ещё сказать, что им обозначает нулевой указатель, но т.к. это для вас никакой информации не несёт, то на данный момент рекомендую об этом не думать. А просто запомнить как некоторый хитрый трюк. В будущих уроках мы остановимся на этой штуке поподробнее.

Практика


Решите предложенные задачи. Для удобства работы сразу переходите в полноэкранный режим

Исследовательские задачи для хакеров:

  1. В каких ситуациях ещё может пригодиться генерация случайных чисел? Напишите ваши варианты в комментарии к этому уроку.
  2. Напишите программу, которая выводит на экран значение целочисленной константы RAND_MAX. Найдите файл stdlib.h на вашем компьютере, найдите значение этой константы в этом файле.
  3. Найдите в интернете описание функций, которые определены в заголовочном файле time.h Вы, конечно, ещё не сможете ими пользоваться, но знать, что такие функции есть, всё равно нужно. Ведь когда-то настанет момент, когда ваших знаний будет достаточно для их использования.
  4. Числа, генерируемые функцией rand(), имеют равномерное распределение. Это значит, что если запускать функцию rand очень много раз и каждый раз записывать, какое число выпало, то количество выпадения различных чисел будет одинаковым. Например, если генерировать только числа 0 и 1, то через 100 запусков примерно 50 раз выпадет ноль и 50 раз единичка. Обратите внимание, что я говорю примерно. Может быть, например, 49 и 51, или 53 и 47. Если рассматривать это в отношении к общему числу запусков, получим (49/100 и 51/100 или 53/100 и 47/100 соответственно). Но чем больше экспериментов мы проведём, тем ближе отношение количество единичек к количеству испытаний будет стремиться к 1/2. Проведите самостоятельно эксперимент с 10, 50 и 100 запусками. Это муторно и долго, если делать руками, но что поделать? В будущем мы напишем программу, чтобы проверить свойство равномерности распределения наших случайных чисел.

Дополнительные материалы

    определённые в заголовочном файле stdlib.h
  1. Хотя я и употребляю везде словосочетание «случайные числа», но на самом деле получить действительно случайные числа – сложная задача. И в компьютерах обычно используются псевдослучайные числа. Подробнее об этом можно прочитать здесь.
  2. Если не терпится узнать хоть что-то про NULL, то почитайте вот этот урок.
  3. Дата 1 января 1970 года особенная. С неё начинается отсчёт эры UNIX. Подробнее об этом и проблемах, которые нас ожидают.

Оставить комментарий

Чтобы код красиво отображался на странице заключайте его в теги [code] здесь писать код [/code]

Комментарии

1 задача. Например, используя генератор случайных чисел можно сделать игральный кубик или подобие игрового автомата.
4 задача. Я постарался сделать, но на 30 остановился. У меня получились следующие результаты:
10 генераций: 0-2, 1-8
20 генераций: 0-12, 1-8
30 генераций: 0-15, 1-15

Т.е. 30 попыток достаточно?

1 задача. Да! Очень хорошие примеры.

4 задача. Нет, 30 попыток недостаточно. То, что у вас получилось 15/15 это совпадение. Я провёл 5 опытов по 30 раз, вот результаты:
1. 15/15
2. 11/19
3. 15/15
4. 17/13
5. 12/18

у вас ошибка в примере с генерацией числе от 80 до 100, программа выдаст значения больше 100! Проверил, вроде бы нет ошибок. Поясните, что вы имеете ввиду?

Здравствуйте! Мой комментарий по поводу формулы расчета диапазона случайных чисел, мы тут посовещались и признали ее не совсем верной. Ваща формула: A + rand()%(B-A+1), при условии которое дано в задаче Введите максимально число, которое может быть сгенерировано следующей конструкцией: int rand_a = 66 + rand()%601; выдает максимальное число в 536. Верная формула: max (a + rand() %b) = a + b - 1 = 666

Человек который мне помогает изучать язык Си, написал тест, для сравнения Вашей формулы, которая дается на сайте и верной формулы.

int main (void)
// A + rand()%(B-A+1)
// 66+rand()%601
// 600-66+1 = 535, а правильный ответ оказался 666
int a, b;
a = 66;
b = 601;

printf("a = %dn", a);
printf("b = %dnn", b);
printf("33[32;22m");
printf("верная формула:33[0mn");
printf("max (a + rand() %%b) = a + b - 1 = %dnn", a+b-1);
printf("33[33;22m");
printf("а вот ваша формула:33[0mn");
printf("max (a + rand() %%b)");
printf(" = b - a + 1 = %dnn", b-a+1);

int i, z, max = 0;
int s = 0, count = 0;
for (i=0; i<1000000; i++)
z = a + rand() %b;
if (z>max)max = z;
if (++count > 2000)
count = 0;
if (++s > 3) s = 0;
print_symbol(s); // строку на экран
fflush (stdout); // обновляем вывод
sleep (0.03); // временная задержка
>
>
printf("nnmax = %dn", max); //max = a+b-1
exit(0);
>
//------------------------------------------------

Подождите-подождите. Почему вы за A и B берёте значения 66 и 601. А и B это начало и конец промежутка из которого вы хотите генерировать числа же.

Ну, давайте, подумаем логически. Дана задача, найти максимальное сгенерированное число, этой конструкции. int rand_a = 66 + rand()%601;
Находим верхнюю и нижнюю границу диапазона, согласно вашей формуле: A + rand()%(B-A+1) она будет int rand_a = 66 + rand()%(601 - 66 + 1), то есть от 66 до 536. Соответственно максимально сгенерированное число получается 536, а валидатор на степике принимает число 666. Да и я сидел запускал программу, раз 20, выпадали числа больше 536. Я засомневался и посоветовался с наставником, он мне по полочкам все разложил, и вышло, что верная формула как для получения диапазона, так и определения максимального сгенерированного числа получается int rand_a = A + rand()%(B + A - 1). Если посмотреть на наш пример, диапазон будет от 66 до 666, соответственно и максимальное сгенерированное число будет 666, это же число и принимает валидатор на степике.

Всё-таки вы не правы.
Ищем нижнюю и верхнюю границы диапазона.

1 способ.
rand()%601 может сгенерировать любое число от 0 до 600. Максимально 600. К нему прибавляется 66. Итого получается 666.

2 способ.
Используем формулу из урока:
A + rand()%(B-A+1)

В задаче имеем
66 + rand()%601

Сопоставим их
A = 66
B-A+1 =601

Подставим вместо А значение 66. Получим
B - 66 +1 = 601

Отсюда B = 601-1+66 = 666. Итого верхняя граница диапазона 666.

У вас ошибка. Вы почему-то считаете, что B = 601. Хотя B вам надо найти. Разберите внимательно 2 способ.

И все таки мы с вами не согласны:)
У нас с вами получается разный подход к вопросу у вас со стороны математики, у нас со стороны информатики.

Как работает генератор случайных чисел?

В общем случае команда выглядит так:

где
a - получившееся случайное число;
b - минимальное значение диапазона случайных чисел;
n - смещение для команды rand();

т.е. сначала выполняется rand() %n, а затем полученный результат
прибавляется к b, и получившаяся сумма записывается в a.

Каким образом работает rand() ?

Она всегда генерирует число, начиная с 0 с заданным смещением, при
этом число 0 тоже учитывается в смещении.

Допустим у нас задано смещение 1:

смещение: 1
число num: 0

т.е. команда a = rand() %1;

всегда будет давать результат 0

Допустим, у нас задано другое смещение 5:

смещение: 1 2 3 4 5
число num: 0 1 2 3 4

т.е. команда a = rand() %5;

будет генерировать число >= 0 и <=4.

Т.е. из-за этой особенности максимальное число, которое генерирует
rand(), всегда на 1 меньше, чем заданное смещение.

Если смещение 25, максимальный результат всегда 24.
Если смещение 100, максимальный результат всегда 99.

И как теперь посчитать максимум?

И если b = 66 и n = 601, максимум всегда будет:
66 + (601 - 1) = 66 + 600 = 666.

На мой взгляд тут нужно не формулу учить, а понимать как работает
генератор случайных чисел, т.е. понимать, как генерируется случайное
число по заданному смещению.

(смещение) n: 1 2 3 4 5 6 7 8 9

Подождите, а с чем конкретно вы не согласны? Выскажите, пожалуйста, тезис.

По-моему мы говорим об одном и том же, но только разными словами. Я не призываю заучивать формулы для отрезка, я призываю разобраться в том, как это работает, используя для примера конкретную задачу.) В любом случае спасибо за неравнодушие и дискуссию!

Задача:
написать программу, генерирующую файл с заданным количеством случайных целых чисел.

Для генерации файла напишем вспомогательную программу, заполняющую файл случайными числами. Количество чисел пользователь будет вводить с клавиатуры.

Решение на языке Си

Функция main будет запрашивать у пользователя количество чисел, вызывать вспомогательную функцию генерации ( writeRandom ) и выводить содержимое файла на экран (функция printFile ):

Для поддержки русских букв в консоли вызывается функция setlocale с параметром "Russian" .

Функции writeRandom и printFile нулевое значение при успешном завершении работы, иначе — код ошибки. Если по каким-то причинам функции writeRandom не удалось записать в файл числа — то приложение завершает работу сразу, не вызывая printFile .

Функция writeRandom открывает файл для записи (с параметром "w+" ), генерируют с помощью стандартной функции rand нужное количество чисел, каждое выводя в файл, и закрывает файл:

Генератор случайных чисел выполняет генерацию на основании некоторого начального числа. Если его не задавать — то при каждом запуске программы мы будем получать одни и теже числа. Функция srand задает это начальное значение, при этом в качестве аргумента пердается результат работы функции time(NULL) , возвращающей текущее значение времени. Таким образом, начальное значение зависит от момента времени и при каждом запуске программы мы будем получать разные числа в файле.

Функция rand возвращает случайное число в большом диапазоне. Чтобы получить число в диапазоне от нуля до 100 мы применяем к результату операцию получения остатка от деления. Если от такого результата отнять еще 50 — то получится число от -50 до 50 , т. к. например:

fwrite(&value, sizeof(int), 1, pFile);

Нужно читать так:
1. возьми байты по адресу &value ;
2. работай с ними группами по sizeof(int) байт, т. е. по столько байт, сколько нужно для представления одного целого числа:
3. одну такую группу запиши в файл с дескриптором pFile .
Функция printFile открывает файл для чтения (с опцией "r" ), считывает значения с помощью функции read , выводит их по очереди на экран и закрывает файл.

Считывание производится до тех пор, пока файл не кончится — при этом функция fread вернет нулевое значение (обычно она возвращает количество считанных байт). Именно поэтому результат fread сравнивается с нулем.

fread(&value, sizeof(int), 1, pFile)

Нужно читать так:
1. Производи чтение с файла с дескриптором pFile группами по sizeof(int) байт, т. е. по столько байт, сколько нужно для представления одного целого числа;
2. Считай 1 группу и помести ее по адресу &value .

Пример диалога пользователя с системой приведен на рисунке:


Исходный код программы целиком:

Решение на С++

Содержимое двоичного файла нельзя понять при открытии в текстовом редакторе и при открытии в двоичном — тоже нелегко. Однако нам надо будет проверять что работа выполнена верно, для этого напишем функцию, выводящую на экран содержимое двочного файла, состоящего из целых чисел:

Она принимает на вход имя файла, возвращает код ошибки (если ошибок нет — то ноль). До тех пор пока не кончится файл ( eof не вернет true ) функция считывает из файла целое число ( sizeof(int) байт) и выводит его на экран.

Для заполнения файла случайными числами также напишем функцию:

В языках программирования обычно предусмотрены функции, позволяющие генерировать случайные числа в определенном по умолчанию диапазоне. На самом деле генерируются не случайные, а так называемые псевдослучайные числа; они выглядят случайно, но вычисляются по вполне конкретной формуле. Но для простоты далее мы все равно будем называть их случайными.

В языке программирования C получить случайное число можно с помощью функции rand() , которая входит в стандартную библиотеку языка. Эта функция не принимает никакие параметры.

Напишите программу, в которой целочисленной переменной присваивается результат выполнения функции rand() . Выведите значение переменной на экран.

Функция rand() возвращает целое число от 0 до значения присвоенного константе RAND_MAX. Значение RAND_MAX зависит от системы и определено в заголовочном файле stdlib.h. Так, например, оно может быть равно 32767 (двухбайтовое целое) или 2147483647 (четырехбайтовое целое).

Определите значение RAND_MAX в вашей системе. Для этого не забудьте подключить к файлу исходного кода заголовочный файл stdlib.h.

Код ниже выводит на экран 50 случайных чисел:

В теле цикла осуществляется переход на новую строку после каждых выведенных на экран пяти чисел. Для этого используется выражение, в котором находится остаток от деления i на 5, результат сравнивается с 0. Чтобы после первого числа не происходил переход на новую строку, iсначала присваивается единица, а не ноль (т.к. 0 делится на 5 без остатка).

Спишите код, приведенный выше. Выполните программу несколько раз, при этом обратите внимание, разные ли результаты вы получаете от выполнения к выполнению.

Вы должны были заметить, что при каждом запуске программы числа остаются одинаковыми. Даже если вы перекомпилируете программу, результат не изменится. Данный эффект связан с тем, что начальное (инициализирующее) число, которое подставляется в формулу вычисления первого и последующих псевдослучайных чисел, для каждой системы всегда одно и то же. Однако это начальное число можно изменить с помощью функции srand() , которой в качестве параметра передается любое целое число. Понятно, что если вы зададите конкретный аргумент для функции, например, srand(1000) , то от вызова к вызову программы числа будут также одни и те же. Хотя и не те, что были бы без srand() . Поэтому появляется проблема, как сделать так, чтобы аргумент для srand() был тоже случайным? Получается замкнутый круг.

Переделайте программу, выводящую на экран 50 случайных чисел так, чтобы сначала у пользователя запрашивалось любое целое число с помощью scanf() , которое передавалось бы в функцию srand() .

Пользователь программы сам может задавать инициализирующее значение. Но чаще всего это не является полноценным выходом из ситуации. Поэтому инициализирующее значение привязывают к какому-либо процессу, протекающему в операционной системе, например, к часам. Время (учитывая не только время суток, но и дату) никогда не бывает одинаковым. Значит значение для srand() , преобразованное в целое из системного времени, будет различным.

Текущее время можно узнать с помощью функции time() , прототип которой описан в файле time.h. Передав time() в качестве параметра NULL, мы получим целое число, которое можно передать в srand() :

Переделайте вашу программу так, чтобы инициализирующее значение зависело от системного времени.

Получение целых случайных чисел в заданных диапазонах

Функция rand() выдает случайное число от 0 до значения RAND_MAX. Что делать, если требуется получать случайные числа в иных диапазонах, например, от 100 до 999?

Сначала рассмотрим более простую ситуацию: получить случайные числа от 0 до 5. Если любое целое число попытаться разделить на 5 нацело, то в качестве остатка можно получить как 0 (когда число делится на 5 без остатка), так и 1, 2, 3, 4. Например, rand() вернула число 283. Применяя к этому числу операцию нахождения остатка от деления на 5, получим 3. Т.е. выражение rand() % 5 дает любое число в диапазоне [0, 5).

Однако, что если надо, чтобы число 5 так же входило в диапазон, т.е. диапазон имеет вид [0, 5]? Логично предположить, что следует найти остаток от деления на 6. При этом более грамотным будет следующее рассуждение: надо находить остаток от деления на размер диапазона. В данном случае он равен шести значениям: 0, 1, 2, 3, 4, 5. Чтобы найти размер диапазона, надо из допустимого максимума вычесть допустимый минимум и прибавить единицу: max - min + 1. Будьте внимательны: если, например, требуется, чтобы указанный в задаче максимум не входил в диапазон, то единицу прибавлять не надо или надо вычитать единицу из максимума.

Напишите программу, выдающую 50 случайных чисел от 0 до 99 включительно.

Итак, мы знаем формулу получения длины диапазона: max - min + 1. Если требуется получить число от 6 до 10 включительно, то длина диапазона будет равна 10 - 6 + 1 = 5. Выражение rand()% 5 даст любое число от 0 до 4 включительно. Но нам надо от 6 до 10. В таком случае достаточно к полученному случайному остатку прибавить 6, т.е. минимум. Другими словами, надо выполнить сдвиг. Действительно для приведенного примера:

  • если остаток был равен 0, то добавляя 6, получаем 6;
  • остаток 1, добавляем 6, получаем 7;
  • остаток 4, прибавляем 6, получаем 10;
  • остатка больше 4 не может быть.

В таком случае формула для получения случайного числа в диапазоне [a, b] выглядит так:

где длина_диапазона вычисляется как b - a + 1, сдвиг является значением a.

В эту формулу также вписываются случаи, когда необходимо получить случайное число от 0 до N, т.е. они являются ее частными случаями.

Выведите на экран ряд случайных чисел, принадлежащих диапазону от 100 до 299 включительно.

С таким же успехом можно получать случайные отрицательные числа. Действительно, если диапазон задан как [-35, -1], то его длина будет равна -1 - (-35) + 1 = 35, что соответствует действительности; выражение получения случайного числа будет выглядеть так:

Так, если остаток от деления составил 0, то мы получим -35, а если 34, то -1. Остальные остатки дадут значения в промежутке от -35 до -1.

Выведите на экран ряд случайных чисел, принадлежащих диапазону от -128 до 127 включительно.

Получение вещественных случайных чисел

Ситуация с вещественными числами выглядит несколько по-иному. Во-первых, мы не можем получить остаток от деления, если делимое или делитель дробные числа. Во вторых при вычислении длины диапазона нельзя прибавлять единицу.

Поясним вторую причину. Допустим диапазон задан как [2.50, 5.30]. Он состоит не из определенного количества чисел (как в случае целых), а из неопределенного (можно сказать, бесконечного) числа значений, т.к. вещественные числа можно представлять с различной степенью точности. Позже выполняя округление все равно будет шанс получить максимальную границу диапазона, поэтому для вычисления длины диапазона достаточно из максимума вычесть минимум.

Если разделить случайное число, преобразованное к вещественному типу, которое выдала функция rand() , на значение константы RAND_MAX, то получится вещественное случайное число от 0 до 1. Теперь, если это число умножить на длину диапазона, то получится число, лежащее в диапазоне от 0 до значения длины диапазона. Далее если прибавить к нему смещение к минимальной границе, то число благополучно впишется в требуемый диапазон. Таким образом формула для получения случайного вещественного числа выглядит так:

Заполните массив случайными числами в диапазоне от 0.51 до 1.00. Выведите значение элементов массива на экран.

Равновероятные случайные числа

Функция rand() генерирует любое случайное число от 0 до RAND_MAX с равной долей вероятности. Другими словами, у числа 100 есть такой же шанс выпасть, как и у числа 25876.

Чтобы доказать это, достаточно написать программу, подсчитывающую количество выпадений каждого из значений. Если выборка (количество "испытуемых") будет достаточно большой, а диапазон (разброс значений) маленьким, то мы должны увидеть, что процент выпадений того или иного значения приблизительно такой же как у других.

В приведенной программе массив из пяти элементов сначала заполняется нулями. Случайные числа генерируются от 0 до 4 включительно. Если выпадает число 0, то увеличивается значение первого элемента массива, если число 1, то второго, и т.д. В конце на экран выводится процент выпадения каждого из чисел.

Спишите данную программу. Посмотрите на результат ее выполнения при различных значениях N: 10, 50, 500, 5000, 50000. Объясните увиденное.

Читайте также: