Відмінності між версіями «Генератори випадкових чисел»
(→Генерация случайных чисел и моделирование) |
(→Генераторы случайных чисел) |
||
Рядок 2: | Рядок 2: | ||
Послідовності випадкових чисел використовуються в програмуванні в найрізноманітніших випадках, починаючи з моделювання (це найбільш часте застосування) і кінчаючи іграми і іншим розважальним програмним забезпеченням. Турбо Паскаль містить вбудовану функцію, звану Random, яка генерує випадкові числа. Як ви побачите в даному розділі, Random - це чудовий генератор випадкових чисел, але для деяких застосувань вам може потрібно два або більш різних генераторів для забезпечення різних наборів випадкових чисел для різних завдань. Крім того, при моделюванні потрібний ассиметрічний або дісбалансний генератор випадкових чисел, який породжує послідовність, зміщену до одного з кінців. Перша частина даного розділу присвячена побудові генераторів випадкових чисел і оцінці їх якості. | Послідовності випадкових чисел використовуються в програмуванні в найрізноманітніших випадках, починаючи з моделювання (це найбільш часте застосування) і кінчаючи іграми і іншим розважальним програмним забезпеченням. Турбо Паскаль містить вбудовану функцію, звану Random, яка генерує випадкові числа. Як ви побачите в даному розділі, Random - це чудовий генератор випадкових чисел, але для деяких застосувань вам може потрібно два або більш різних генераторів для забезпечення різних наборів випадкових чисел для різних завдань. Крім того, при моделюванні потрібний ассиметрічний або дісбалансний генератор випадкових чисел, який породжує послідовність, зміщену до одного з кінців. Перша частина даного розділу присвячена побудові генераторів випадкових чисел і оцінці їх якості. | ||
− | == | + | == Генератори випадкових чисел == |
− | + | Технічно термін "генератор випадкових чисел" - це абсурд; числа само по собі не є випадковими. Наприклад, 100 - це випадкове число? А 25? Що насправді означає цей термін, так це те, що створюється послідовність чисел, що з'являються випадковим чином. Це породжує складніше питання: що таке послідовність випадкових чисел? Єдино правильна відповідь: послідовність випадкових чисел _ це послідовність, в якій всі елементи є незв'язаними. Це визначення приводить до такого парадоксу, що будь-яка послідовність може бути як випадковою, так і невипадковою залежно від того, як ця послідовність отримана. Наприклад, наступний рядок чисел | |
− | 1 2 3 4 5 6 7 8 9 0 | + | 1 2 3 4 5 6 7 8 9 0 була отримана друкуванням верхнього рядка клавіатури по порядку, таким чином послідовність звичайно не може розглядатися як що згенерувала випадковим чином. Але як бути, якщо ви отримаєте ту ж саму послідовність, виймаючи пронумерований тенісні кулі з боченка. В даному випадку це вже випадковим чином послідовність, що згенерувала. Даний приклад показує, що випадковість послідовності залежить від того, як вона була отримана, а не від неї самої. |
− | + | Пам'ятаєте, що послідовність чисел, що згенерувала комп'ютером, є детермінованою: кожне число, окрім першого, залежить від попередніх чисел. Технічно це означає, що комп'ютером може згенерувати тільки квазівипадкова послідовність чисел. Проте, це досить для більшості завдань і в даній книзі такі послідовності для простоти називатимуться випадковими. | |
− | + | У загальному випадку вважається добре, коли числа в послідовності випадкових чисел розподілені рівномірно (не плутайте це з нормальним розподілом або колоколообразной кривою). При рівномірному розподілі всі події рівноімовірні так, що діаграма рівномірного розподілу прагне до прямої горизонтальної лінії, а не до кривій. | |
− | До широкого | + | До широкого розповсюдження комп'ютерів всякий раз, коли необхідні були випадкові числа, вони виходили або киданням гральних кісток, або вийманням пронумерованих куль з ящика. У 1955 році фірма RAND опублікувала таблицю з 1 мільйона випадкових чисел, отриманих за допомогою обчислювальної машини. На ранній стадії розвитку обчислювальної техніки було розроблено багато методів генерації випадкових чисел, але більшість з них не знайшла застосування. |
− | Один | + | Один дуже цікавий метод був розроблений Джоном фон Нейманом; його часто називають середньоквадратичним. У даному методі попереднє випадкове число зводиться в квадрат, а потім з результату виділяються середні цифри. Наприклад, якщо ви створюєте числа з трьох цифр, а попереднє число було 121, то зведення в квадрат дає результат 14641. Виділення трьох середніх цифр дає наступне випадкове число 464. Недоліком даного методу є те, що він має дуже короткий період повторення, званий циклом. З даної причини даний метод сьогодні не використовується. |
− | + | Зараз найчастіше застосовується метод генерації випадкових чисел, що грунтується на використанні рівняння | |
R = (aR +c)modm | R = (aR +c)modm | ||
n+1 n | n+1 n | ||
− | при | + | при виконанні наступних умов |
R>0 | R>0 | ||
Рядок 18: | Рядок 18: | ||
c>0 | c>0 | ||
m>R , a и c | m>R , a и c | ||
− | + | Відзначимо, що R - це попереднє число, а R - наступне. Даний метод іноді називають лінійним порівняльним методом. Формула така проста, що ви можете подумати, що генерувати випадкові числа просто. Проте, це пастка: наскільки добре працює дана формула, дуже сильно залежить від значення а, з і m. Вибір значень іноді більшою мірою мистецтво, ніж наука. Існують складні правила, які можуть допомогти вам вибрати значення; проте, ми розглянемо лише декілька простих правил і прикладів. | |
− | Модуль (m) | + | Модуль (m) повинен бути досить великим, оскільки він визначає область випадкових чисел. Операція узяття по модулю породжує залишок від ділення числа на модуль. Отже, 10 по модулю 4 рівне 2. Таким чином, якщо модуль рівний 12, то формулою породжуються числа від 0 до 11, а якщо модуль рівний 21425, то породжуються числа від 0 до 21424. Вибір множника а і прирости з є дуже складним завданням. У загальному випадку множник може бути задоволене великим, а приріст - маленьким. Безліч спроб і перевірок необхідна, щоб створити хороший генератор. |
− | + | Як перший приклад тут приведений один з найчастіше використовуваних генераторів випадкових чисел. Рівняння, показане в Rаn1 використовується як основа для генератора випадкових чисел у ряді популярних мов. | |
var | var | ||
a1: integer; { установка до первого вызова Ran1 } | a1: integer; { установка до первого вызова Ran1 } | ||
Рядок 31: | Рядок 31: | ||
Ran1 := Abs(t/32749); | Ran1 := Abs(t/32749); | ||
end; {Rea1} | end; {Rea1} | ||
− | + | Дана функція має три головні особливості. По-перше, випадкові числа насправді є цілими, хоча функція повертає дійсні числа. Даний метод працює з цілими числами, але генератори випадкових чисел, як це прийнято, повинні повертати числа в межах від 0 до 1, що означає, що це повинні бути числа з плаваючою комою. | |
− | + | По-друге, початкове значення задається через глобальну змінну а1. До першого виклику Ran1 змінна а1 повинна бути встановлена в 1. | |
− | + | По-третє, в Ran1 випадкові числа діляться на модуль перш, ніж вони будуть повернені функцією, для того, щоб числа лежали в області від 0 до 1. Якщо ви поцікавитеся значенням глобальної змінної а1 до повернення рядка, то воно повинне лежати в області від 0 до 32748. Отже, коли а1 ділиться на 32749, отримане число буде більше або рівне 0 і менше 1. | |
− | + | Багато генераторів випадкових чисел не застосовні, оскільки вони породжують не рівномірний розподіл або мають короткий цикл повторення. Навіть коли ці недоліки не дуже впадають в очі, вони можуть породити змішаний результат, якщо такий генератор використовується знову і знову. Рішення полягає в тому, щоб створити різні генератори і застосовувати їх індивідуально або спільно для отримання якісніших послідовностей випадкових чисел. Застосування декількох генераторів може згладити розподіл послідовності за рахунок зменшення малих змішень окремих генераторів. Далі приведена функція генерування випадкових чисел, звана Ran2, яка породжує хороший розподіл: | |
var | var | ||
− | a2:integer; { | + | a2:integer; { встановити в значення 203 до першого виклику |
Ran2 } | Ran2 } | ||
function Ran2: real; | function Ran2: real; | ||
Рядок 46: | Рядок 46: | ||
Ran2 := Abc(t/17417); | Ran2 := Abc(t/17417); | ||
end; {Ran2} | end; {Ran2} | ||
− | + | Обидва цих генератора випадкових чисел породжують хороші послідовності випадкових чисел. Проте залишаються питання: чи достатньо "випадковою" є послідовність? Чи хороші дані генератори? | |
== Определение качества генераторов == | == Определение качества генераторов == |
Версія за 08:04, 5 червня 2009
Зміст
Генерація випадкових чисел і моделювання
Послідовності випадкових чисел використовуються в програмуванні в найрізноманітніших випадках, починаючи з моделювання (це найбільш часте застосування) і кінчаючи іграми і іншим розважальним програмним забезпеченням. Турбо Паскаль містить вбудовану функцію, звану Random, яка генерує випадкові числа. Як ви побачите в даному розділі, Random - це чудовий генератор випадкових чисел, але для деяких застосувань вам може потрібно два або більш різних генераторів для забезпечення різних наборів випадкових чисел для різних завдань. Крім того, при моделюванні потрібний ассиметрічний або дісбалансний генератор випадкових чисел, який породжує послідовність, зміщену до одного з кінців. Перша частина даного розділу присвячена побудові генераторів випадкових чисел і оцінці їх якості.
Генератори випадкових чисел
Технічно термін "генератор випадкових чисел" - це абсурд; числа само по собі не є випадковими. Наприклад, 100 - це випадкове число? А 25? Що насправді означає цей термін, так це те, що створюється послідовність чисел, що з'являються випадковим чином. Це породжує складніше питання: що таке послідовність випадкових чисел? Єдино правильна відповідь: послідовність випадкових чисел _ це послідовність, в якій всі елементи є незв'язаними. Це визначення приводить до такого парадоксу, що будь-яка послідовність може бути як випадковою, так і невипадковою залежно від того, як ця послідовність отримана. Наприклад, наступний рядок чисел 1 2 3 4 5 6 7 8 9 0 була отримана друкуванням верхнього рядка клавіатури по порядку, таким чином послідовність звичайно не може розглядатися як що згенерувала випадковим чином. Але як бути, якщо ви отримаєте ту ж саму послідовність, виймаючи пронумерований тенісні кулі з боченка. В даному випадку це вже випадковим чином послідовність, що згенерувала. Даний приклад показує, що випадковість послідовності залежить від того, як вона була отримана, а не від неї самої. Пам'ятаєте, що послідовність чисел, що згенерувала комп'ютером, є детермінованою: кожне число, окрім першого, залежить від попередніх чисел. Технічно це означає, що комп'ютером може згенерувати тільки квазівипадкова послідовність чисел. Проте, це досить для більшості завдань і в даній книзі такі послідовності для простоти називатимуться випадковими. У загальному випадку вважається добре, коли числа в послідовності випадкових чисел розподілені рівномірно (не плутайте це з нормальним розподілом або колоколообразной кривою). При рівномірному розподілі всі події рівноімовірні так, що діаграма рівномірного розподілу прагне до прямої горизонтальної лінії, а не до кривій. До широкого розповсюдження комп'ютерів всякий раз, коли необхідні були випадкові числа, вони виходили або киданням гральних кісток, або вийманням пронумерованих куль з ящика. У 1955 році фірма RAND опублікувала таблицю з 1 мільйона випадкових чисел, отриманих за допомогою обчислювальної машини. На ранній стадії розвитку обчислювальної техніки було розроблено багато методів генерації випадкових чисел, але більшість з них не знайшла застосування. Один дуже цікавий метод був розроблений Джоном фон Нейманом; його часто називають середньоквадратичним. У даному методі попереднє випадкове число зводиться в квадрат, а потім з результату виділяються середні цифри. Наприклад, якщо ви створюєте числа з трьох цифр, а попереднє число було 121, то зведення в квадрат дає результат 14641. Виділення трьох середніх цифр дає наступне випадкове число 464. Недоліком даного методу є те, що він має дуже короткий період повторення, званий циклом. З даної причини даний метод сьогодні не використовується. Зараз найчастіше застосовується метод генерації випадкових чисел, що грунтується на використанні рівняння
R = (aR +c)modm n+1 n
при виконанні наступних умов
R>0 a>0 c>0 m>R , a и c
Відзначимо, що R - це попереднє число, а R - наступне. Даний метод іноді називають лінійним порівняльним методом. Формула така проста, що ви можете подумати, що генерувати випадкові числа просто. Проте, це пастка: наскільки добре працює дана формула, дуже сильно залежить від значення а, з і m. Вибір значень іноді більшою мірою мистецтво, ніж наука. Існують складні правила, які можуть допомогти вам вибрати значення; проте, ми розглянемо лише декілька простих правил і прикладів. Модуль (m) повинен бути досить великим, оскільки він визначає область випадкових чисел. Операція узяття по модулю породжує залишок від ділення числа на модуль. Отже, 10 по модулю 4 рівне 2. Таким чином, якщо модуль рівний 12, то формулою породжуються числа від 0 до 11, а якщо модуль рівний 21425, то породжуються числа від 0 до 21424. Вибір множника а і прирости з є дуже складним завданням. У загальному випадку множник може бути задоволене великим, а приріст - маленьким. Безліч спроб і перевірок необхідна, щоб створити хороший генератор. Як перший приклад тут приведений один з найчастіше використовуваних генераторів випадкових чисел. Рівняння, показане в Rаn1 використовується як основа для генератора випадкових чисел у ряді популярних мов.
var a1: integer; { установка до первого вызова Ran1 } function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Rea1}
Дана функція має три головні особливості. По-перше, випадкові числа насправді є цілими, хоча функція повертає дійсні числа. Даний метод працює з цілими числами, але генератори випадкових чисел, як це прийнято, повинні повертати числа в межах від 0 до 1, що означає, що це повинні бути числа з плаваючою комою. По-друге, початкове значення задається через глобальну змінну а1. До першого виклику Ran1 змінна а1 повинна бути встановлена в 1. По-третє, в Ran1 випадкові числа діляться на модуль перш, ніж вони будуть повернені функцією, для того, щоб числа лежали в області від 0 до 1. Якщо ви поцікавитеся значенням глобальної змінної а1 до повернення рядка, то воно повинне лежати в області від 0 до 32748. Отже, коли а1 ділиться на 32749, отримане число буде більше або рівне 0 і менше 1. Багато генераторів випадкових чисел не застосовні, оскільки вони породжують не рівномірний розподіл або мають короткий цикл повторення. Навіть коли ці недоліки не дуже впадають в очі, вони можуть породити змішаний результат, якщо такий генератор використовується знову і знову. Рішення полягає в тому, щоб створити різні генератори і застосовувати їх індивідуально або спільно для отримання якісніших послідовностей випадкових чисел. Застосування декількох генераторів може згладити розподіл послідовності за рахунок зменшення малих змішень окремих генераторів. Далі приведена функція генерування випадкових чисел, звана Ran2, яка породжує хороший розподіл:
var a2:integer; { встановити в значення 203 до першого виклику Ran2 } function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abc(t/17417); end; {Ran2}
Обидва цих генератора випадкових чисел породжують хороші послідовності випадкових чисел. Проте залишаються питання: чи достатньо "випадковою" є послідовність? Чи хороші дані генератори?
Определение качества генераторов
Вы можете применить различные тексты для определения случайности последовательности чисел. Ни один из тестов не скажет, что последовательность является случайной, однако, он скажет, если она не является таковой. Тесты могут выявить не случайные последовательности, но, если тест не нашел недостатков, это не означает, что данная последовательность действительно случайная. Тесты, однако, повышают уверенность в генераторе случайных чисел, который породил последовательность. Теперь мы кратко рассмотрим несколько простых способов тестирования последовательностей. Для начала рассмотрим способ определения того, насколько близко распределение чисел в последовательности соответствует ожидаемому. Например, вы пытаетесь генерировать последовательность случайных чисел от 0 до 9. Вероятность появления каждой цифры равна 1/10. Пусть была сгенерирована следующая последовательность
9 1 8 2 4 6 3 7 5 2 9 0 4 2 4 7 8 6 2
Если вы подсчитаете число появлений каждой цифры, то получите результат
Цифры Число появлений 0 1 1 1 2 4 3 1 4 3 5 1 6 2 7 2 8 3 9 2
Далее вам следует ответить самому себе на вопрос, достаточно ли похоже данное распределение на ожидаемое вами. Помните: если генератор случайных чисел хороший, он генерирует последовательности случайно. В истинно случайном варианте все последовательности возможны. Действительно, как какая-то последовательность может быть не случайной, если любая последовательность возможна? Просто некоторые последовательности менее похожи на то, какой должна быть случайная последовательность, чем другие. Вы можете определить вероятность того, что данная последовательность является случайной, используя критерий хи-квадрат. В критерии хи-квадрат ожидаемое количество вычитается из наблюдаемого количества встреч числа в сгенерированной последовательности. Этот результат называется V. Вы можете использовать Vдля нахождения процента в таблице значений хи-квадрат. Этот процент определяет вероятность того, что была порождена случайная последовательность. Малая таблица хи-квадрат приведена на рис.7-1; вы можете найти полные таблицы в большинстве книг по статистике
p=99% p=95% p=75% p=50% p=25% p=5% n=5 0.5543 1.1455 2.675 4.351 6.626 11.07 n=10 2.558 3.940 6.737 9.342 12.55 18.31 n=15 5.229 7.261 11.04 14.34 18.25 25.00 n=20 8.260 10.85 15.45 19.34 23.83 31.41 n=30 14.95 18.49 24.48 29.34 34.80 43.77
Выбранные значения хи-квадрат. Для определения вероятности того, что последовательность не случайная, найдите строку в таблице, показанной на рис.7-1, с числом элементов последовательности; в данном случае это 20. Затем ищите число по строке, которое больше V. В данном случае это колонка 1. Это означает, что существует вероятность 99% того, что пример из 20 элементов будет иметь V больше 8,260. С другой стороны это означает, что существует вероятность только в 1% того, что проверяемая последовательность была сгенерирована случайным образом. Чтобы пройти через критерий хи-квадрат, вероятность V должна снизиться до уровня от 25% до 75%. Однако вы можете противопоставить этому выводу вопрос: Так как все последовательности возможны, как может данная последовательность иметь только однопроцентный шанс быть законной? Ответ такой: это всего лишь вероятность. Фактически, если вы применяете критерий хи-квадрат, вам следует получить несколько различных последовательностей и усредненный результат, чтобы избежать отвержения хорошего генератора случайных чисел. Любая единичная последовательность может быть отвергнута, но ряд различных последовательностей после усреднения должен давать хороший результат. С другой стороны, последовательность может пройти через критерий хи-квадрат и не быть случайной. Например, последовательность 1 3 5 7 9 1 3 5 7 9 пройдет критерий хи-квадрат, но она выглядит не очень случайной. В данном случае сгенерирован пробег по диапазону значений. Пробег - это просто возрастающая или убывающая последовательность чисел с произвольным интервалом. В данном случае каждая группа из пяти цифр представляет собой возрастающую последовательность и как таковая не может считаться случайной последовательностью. Можно создать тест для обнаружения такой ситуации, но это выходит за рамки данной книги. Другой характеристикой, подлежащей оценке, является длина периода: то есть, как много чисел может быть сгенерировано до начала повторения последовательности. Все машинные генераторы случайных чисел всегда генерировали повторяющуюся последовательность. Чем длиннее период, тем лучше генератор. Даже если частота чисел внутри периода распределена равномерно, числа не образуют случайную серию, так как действительно случайная серия не может достаточно повторяться. В общем случае период в несколько тысяч чисел удовлетворяет большинству применений. Тест для выяснения периода может быть разработан. Различные другие тесты могут быть применены для определения качества генератора случайных чисел. Наверное, можно написать больше программ для проверки генераторов случайных чисел, чем создать самих генераторов. Рассмотрим еще один тест, который позволит вам проверить генератор случайных чисел "визуально", используя диаграмму для демонстрации характеристик сгенерированной последовательности. В идеале диаграмма должна основываться на частоте каждого числа. Однако так как генератор может порождать тысячи различных чисел, это не выполнимо. Вместо этого будут создаваться диаграммы, сгруппированные до десяти цифрам. Например, так как порождаемые числа лежат в области от 0 до 1, число 0.9365783 будет включено в группу 9, а число 0.34523445 будет включено в группу 3. Это означает, что диаграмма вывода случайных чисел имеет 10 линий, каждая из которых представляет число попаданий в группу. Программа также выводит среднее значение последовательности, которое может быть использовано для обнаружения смешения. Как и все другие программы данной главы, следующая программа выполняется только на персональном компьютере IBM PC, который имеет адаптер цветного графического дисплея. Разработанные ранее функции Ran1 и Ran2, а также встроенная функция Турбо Паскаля Random, продемонстрированы рядом для сравнения.
{ программа, которая сравнивает три генератора случайных чисел } program RanGenerator; uses Graph; const COUNT = 1000; var freg1, freg2, freg3: array[0..9] of integer; a2, a1: integer; f, f2, f3: real; r, r2, r3: real; y, x: integer; GraphDriver, GraphMode: integer; {отображение графического вывода} procedure Display; var t : integer; begin for t := 0 to 9 do begin Line(t*10, 180, t*10, 180-freg1[t]); Line(t*10+110, 180, t*10+110, 180-freg2[t]); Line(t*10+220, 180, t*10+220, 180-freg3[t]); end; end; {Display} function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Ran1} function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abs(t/17417); end; {Ran2} begin { переключение на графический режим, используя режим 4 CGA/EGA } GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); OutTextXy(80, 10, 'Comparison of Random'); OutTextXy(96, 20, 'Number Generators'); { прорисовать базовые линии } Line(0, 180, 90, 180); Line(110, 180, 200, 180); Line(220, 180, 310, 180); OutTextXy(30, 190, 'Random Ran1 Ran2'); {инициализация переменных генераторов случайных чисел } a1:=1; a2:=203; f := 0; f2 := 0; f3 := 0; for x := 0 to 9 do begin { инициализация матриц частот } freg1[x] := 0; freg2[x] := 0; freg3[x] := 0; end; for x := 1 to COUNT do begin r:=Random; { взять случайное число } f:=f+r; { прибавить для вычисления среднего } y:=Trunc(r*10);{ преобразовать в целое число от 0 до 9 } freg1[y]:=freg1[y]+1;{ увеличить счетчик частоты } r2 := Ran1; { взять случайное число } f2:=f2+r2; { прибавить для вычисления среднег } y:=Trunc(r2*10);{ преобразовать в целое число от 0 до 9} freg2[y]:=freg2[y]+1;{ увеличить счетчик частоты } r3 := Ran2; { взять случайное число } f3:=f3+r3; { прибавить для вычисления среднего } y:=Trunc(r3*10);{ преобразовать в целое число от 0 до 9} freg3[y]:=freg3[y]+1;{увеличить счетчик частоты } Display; { отобразить счетчики частот } end; ReadLn; RestoreCrtMode; WriteLn('mean of Random is: ', f/COUNT); WriteLn('mean of Ran1 is: ', f2/COUNT); WriteLn('mean of Ran2 is: ', f3/COUNT); end.
В данной программе каждая функция генерирует 1000 чисел и на основе этого создаются таблицы частот. Процедура Display рисует все три матрицы частот на экране после генерации каждого случайного числа так, что вы можете наблюдать рост частот. На рис.7-2 показан вывод каждого генератора случайных чисел после генерации 1000 чисел. Средние значения равны 0,489932 у Ran1, 0,4858311 y Ran2 и 0,494014 у Random. Это приемлемо.
Сравнение генераторов случайных чисел
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L-+-+-+-+-+-+-+-+-- L-+-+-+-+-+-+-+-+-- L-+-+-+-+-+-+-+-+- Random Ran1 Ran2 ----------------------------------------------------------.
Вывод из программы отображения работы генераторов
случайных чисел.
Для эффективного использования программы вы должны наблюдать как за формой диаграммы, так и за динамикой роста, чтобы выявить короткие повторяющиеся циклы. Например, Ran2 генерирует значительно меньше чисел в области от 0,9 до 0,999999, чем Random и Ran1. Конечно данный текст не является всеобъемлющим, но он поможет вам понять способ, которым генератор порождает числа, и ускорит процесс анализа генераторов.
Использование нескольких генераторов
Один простой метод, который улучшает свойства случайных последовательностей, порождаемых тремя генераторами, заключается в комбинировании их под управлением одной главной функции. Данная функция выбирает между двумя из них, основываясь на результате третьей. С помощью этого метода вы можете получить очень длинный период и уменьшить влияние циклов и смещений. Функция, называемая CombRandom, показанная здесь, осуществляет комбинирование выводов генераторов Ran1, Ran2, Random:
{данная функция использует три генератора для возврата одного случайного числа} function CombRandom: real; var f: real; begin f := Ran2; if f>0.5 thenCombRandom := Random else CombRandom := Ran1; { случайный выбор генератора } end; {CombRandom}
Результат Ran2 используется для того, чтобы решить, Ran1 или Random выдаст значение главной функции CombRandom. При таком методе период главной функции равен или больше суммы периодов Random и Ran1. Таким образом, данный метод делает возможным порождение последовательности с очень длинным периодом. Можно легко изменять смесь Random и Ran1 изменением константы в операторе if, чтобы получить желаемое вами распределение между этими двумя генераторами. Кроме того, вы можете добавить дополнительные генераторы и осуществлять выбор между ними для получения еще более длинного периода. Далее следует программа для отображения диаграммы CompRandom и ее среднего значения. На рис.7-3 показана финальная диаграмма после генерации 1000 чисел. Среднее значение CombRandom равно 0,493361.
{ данная программа демонстрирует комбинированный вывод трех генераторов случайных чисел } program MultiRandom; uses Graph; const COUNT=1000; var freg: array[0..9] of integer; a2,a1: integer; f, r: real; y, x: integer; GraphDriver, GraphMode: integer; { отображение графического представления работы генераторов } procedure Display; var t: integer; begin for t := 0 to 9 do Line(t*10+110, 180, t*10+110, 180-freg[t]); end; {Display} function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Ran1} function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abs(t/17417); end; {Ran2} {данная функция использует три генератора для возврата одного случайного числа } function CombRandom real; var t: real; begin f := Ran2; if f>0.5 then CombRandom := Random else CombRandom := Ran1; {случайный выбор генератора } end; {CombRandom} begin { переключение на графический режим, используя режим 4 CGA/EGA } GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); OutTextXy(48, 10, 'вывод, полученный комбинированием '); OutTextXy(40,20, 'три генератора случайных чисел '); Line(110, 180, 200, 180); a1:=1; a2:=203; {инициализация переменных для генераторов случайных чисел } f := 0; for x:=0 to 9 do freg[x]:=0; {инициализация матрицы частот} for x := 1 to COUNT do begin r:=CombRandom; { взять случайное число } f:=f+1; { прибавить для вычисления среднего } y:=Trunc(r*10);{ преобразовать в целое число от 0 до 9} freg[y]:=freg[y]+1;{ увеличить счетчик частоты } Display; end; ReadLn; RestoreCrtMode; WriteLn('Среднее случайное число равно : ', f/COUNT); end.
.
Вывод, полученный комбинированием трех генераторов случайных чисел.
¦ ¦ ¦ ¦ ¦ ¦ ¦¦¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦¦¦¦ L++++++++ ------------------------------------------------------
Финальное отображение функции CombRandom
Моделирование
Оставшаяся часть данной главы посвящена применениям генераторов случайных чисел для моделирования на компьютерах. Промоделировать можно все; успех моделирования зависит главным образом от того, насколько хорошо программист понял ситуацию, которую надо моделировать. Так как реальные ситуации часто имеют тысячи переменных, многие вещи с трудом поддаются моделированию. Однако, существуют ситуации, которые очень хорошо подходят для моделирования. Моделирование важно по двум причинам. Во-первых, оно позволяет вам изменять параметры моделирования для проверки и наблюдения возможных результатов в то время, как в реальности такие эксперименты могут быть и дорогими и опасными. Например, моделирование атомной электростанции может быть использовано для проверки влияния различных типов отказов без какой-либо опасности. Во-вторых, моделирование позволяет нам создавать ситуации, которые не могут произойти в реальной жизни. Например, психологи могут захотеть изучить влияние непрерывного роста интеллекта мыши до человеческого, чтобы определить, когда мышь будет проходить лабиринт наиболее быстро. Хотя это не может быть сделано в реальной жизни, моделирование может помочь проникновению в природу соотношения интеллекта и инстинкта. Далее разберем первый из двух примеров, в которых используются генераторы случайных чисел.
Моделирование очередей обслуживания
В первом примере моделируется обслуживание в бакалейной лавке. Предположим, что лавка открыта 10 часов в день с пиковыми часами с 12 до 13 и с 17 до 18 часов. Период с 12 до 13 часов имеет нагрузку в два раза, а с 17 до 18 - в три раза больше обычной. При моделировании один генератор "порождает" покупателей, второй генератор определяет время обслуживания покупателя, а третий решает, в какую очередь пойдет покупатель. Цель моделирования состоит в том, чтобы помочь управляющему найти оптимальное число очередей, которые должны работать в обычный день при условии, что число людей в очереди в любое время не превышало бы 10 и кассиры не ожидали бы покупателей. Ключ к данному типу моделирования состоит в создании многих процессов. Хотя Турбо Паскаль непосредственно не поддерживает параллельности, вы можете моделировать с помощью множества процессов или с помощью главной программы с циклами. Далее показана программа с ее глобальными данными для моделирования очередей без поддержки процедур и функций:
var gueues, count: array[0..9] of integer; open: array[0..9] of boolean; cust, time: integer; a1, a2: integer; y, x: integer; change: boolean; GraphDiver, GraphMode: integer; begin {переключение на графику, используя режим 4 CGA/EGA} GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); a1:=1; a2:=203; {инициализация переменных генератора случайных чисел} change := FALSE; cust := 0; time := 0; for x:=0 to 9 do begin gueues[x]:=0; {инициализация очереди } open[x]:=FALSE; { нет покупателей или очередей в начале дня } count[x]:=0; {счетчик очереди } end; OutTextXy(155, 190, '1 10'); OutTextXy(8,190,'Check-out lines: '); { теперь начинается день открытием первой очереди } open[0] := TRUE; repeat AddCust; AddQueue; Display; CheckOut; Display; if (time>30) and (time<50) then AddCust; if (time>70) and (nime<80) then begin AddCust; AddCust; end; time := time+1; until time>100; { конец дня } ReadLn; RestoreCrtMode; end.
Элемент Graph.P включен, чтобы программа могла использовать графические функции. Главный цикл управляет всем моделированием:
repeat AddCust; AddQueue; Display; CheckOut; Display; if (time>30) and (time<50) then AddCust; if (time>70) and (time<80) then begin AddCust; AddCust; end; time := time+1; until time>100; { конец дня }
Функция AddCust использует либо Ran1, либо Ran2 для генерации числа покупателей, встающих в очереди при каждом запросе. Функция AddQuece используется для помещения покупателей в очереди в соответствии с результатом Ran2, а также открывает новые очереди, если все существующие переполнены. Функция Display отображает программу моделирования. Checkout использует Ran2 для назначения каждого покупателя в очередь с соответствующим увеличением счетчика очереди; каждый вызов уменьшает счетчик на 1. Когда счетчик покупателей равен 0, очередь становится пустой. Переменная time (время) изменяет интенсивность, с которой генерируются покупатели, для того, чтобы отследить часовые пики. Каждый проход по циклу представляет одну десятую часа. На рис.7-4, 7-5 и 7-6 показаны состояния очередей, когда time=28, time=60 и time=88, что соответствует нормальному времени, концу первого пика и концу второго пика, соответственно. Отметим, что в конце второго пика требуется максимум пять очередей, Если моделирующая программа написана правильно, то в бакалейной лавке оставшиеся пять очередей не нужны.
gueue 1: 10 time: 28 gueue 2: 8 gueue 3: 0 gueue 4: 0 gueue 5: 0 gueue 6: 0 gueue 7: 0 gueue 8: 0 gueue 9: 0 gueue 10: 0
Очередь ¦
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ 1 10
Рис.7-4. Состояние очередей, когда time=28:
gueue 1: 6 time: 60 gueue 2: 8 gueue 3: 8 gueue 4: 1 gueue 5: 0 gueue 6: 0 gueue 7: 0 gueue 8: 0 gueue 9: 0 gueue 10: 0
Очередь ¦ ¦
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
1 10
Рис.7-5. Состояние очередей, когда time=60:
gueue 1: 8 time: 80 gueue 2: 9 gueue 3: 6 gueue 4: 6 gueue 5: 7 gueue 6: 0 gueue 7: 0 gueue 8: 0 gueue 9: 0 gueue 10: 0
Очередь ¦
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
1 10
Рис.7-6. Состояние очередей, когда time=88: Вы можете непосредственно управлять различными переменными в программе. Во-первых, вы можете изменить путь и число прибывающих покупателей. Вы также можете изменить функцию AddCost, чтобы она возвращала число покупателей в пиковые часы с большим или меньшим постепенным увеличением или уменьшением. Программа предполагает, что покупатели случайным образом выбирают, в какую очередь им встать. Такой подход справедлив для одних покупателей, а другие будут выбирать самую короткую очередь. Вы можете учесть это, изменив функцию AddQueue так, чтобы она в некоторых случаях помещала покупателей в самую короткую очередь, а в некоторых - случайным образом. При моделировании не учитываются такие случайности, как упавшая булка или буйный покупатель в очереди, которые вызывают временные остановки очереди. Целиком программа выглядит следующим образом:
program simulation; {моделирование очередей в бакалейной лавке } uses Graph; var gueues, count: array[0..9] of integer; open: array[0..9] of boolean; cust, time: integer; a1, a2: integer; y,x: integer; change: boolean; GraphDriver, GraphMode: integer; function Ran1: real; var t: real; begin t := (a1*32749+3) mod 32749; a1 := Trunc(t); Ran1 := Abs(t/32749); end; {Ran1} function Ran2: real; var t: real; begin t := (a2*10001+3) mod 17417; a2 := Trunc(t); Ran2 := Abs(t/17417); end; {Ran2} function CombRandom: real; {random selection of generators} 2 var f: real; begin f := Ran2; if f>0.5 then CombRandom := Random else CombRandom:=Ran1;{случайный выбор генераторов} end; {CombRandom} { добавление покупателей в очередь } procedure AddCust; var f, r: real; begin if change then f:=Random {переключение между двумя } else f := Ran2; {генераторами } if f>0.5 then if f>0.6 then cust:=cust+1 {добавить одного покупателя} else if f>0.7 then cust:=cust+2 {два покупателя} else if f<0.8 then cust:=cust+3 {три покупателя } else cust := cust+4; {четыре покупателя } end; {AddCust} { обслуживание покупателя } Procedure CheckOut; var t: integer; begin for t := 0 to 9 do begin if gueues[t]<>0 then begin {получить время обслуживания } while count[t]=0 do count[t]:=Trunc(Ran1+5); {новый покупатель требует времени обслуживания } count[t]:=count[t]-1; if count[t]=0 then gueues[t]:=gueues[t]-1; {удаление покупателя} end; if gueues[t]=0 then open[t]:=FALSE;{закрытие очереди} end; end; {CheckOut} {возвращается TRUE, если очередь переполнена } function AllFull: Boolean; var t: integer; begin AllFull := TRUE; for t := 0 to 9 do if (gueues[t]<10) and open[t] then AllFull:=FALSE; end; {AllFull} {данная процедура вводит новые очереди } procedure AddQueue; var t, line: integer; done: Boolean; begin done := FALSE; while cust<>0 do begin if AllFull then begin t:=0; repeat if not open[t] then begin open[t]:=TRUE; done:=TRUE; end; t:=T+1; until done or (t=9); end else begin Line:=Trunc(Ran2*10); if open[line] and (gueues[line]<10) then begin gueues[line]:=gueues[line]+1; cust:=cust-1; end; end; if AllFull and open[9] then cust:=0; {all full} end; end; {AddQueue} {очистить символы длины, начиная с позиции Х, У } procedure ClrVed(x,y,len: integer); var i: integer; s: string[80]; begin for i := 1 to len do s := concat(Chr(219), Chr(219)); SetColor(0); OutTextXy(x, y, s); SetColor(2); end; {ClrVid} {отображение экрана результатов моделирования очереди } procedure Display; var t: integer; value: string[80]; begin cirVid(170, 10, 3); str(time, value); OutTextXy(120, 10, 'Time: '); OutTextXy(170, 10, value); for t := 0 to 9 do begin {erase old line} SetColor(0); Line((t*10)+160, 180, (t*10)+160, 180); {нарисовать новое состояние моделирования } SetColor(2); Circle((t*10)+160, 180, 3); Line((t*10)+160, 180, (t*10)+160, 180-gueues[t]*10); {дать также текстовый вывод } OutTextXy(8, t*10, 'gueue'); str(t+1, value); value := concat(value, ':'); OutTextXy(56, t*10, value); ClrVid(80, t*10, 3); str(gueues[t], value); OutTextXy(80, t*10, value); end; end; {Display} begin {переключение на графику, используя режим 4 CGA/EGA } GraphDriver := CGA; GraphMode := CGAC1; InitGraph(GraphDriver, GraphMode, ); SetColor(2); SetLineStyle(SolidLn, 0, NormWidth); a1:=1; a2:=203; {инициализация переменных генератора случайных чисел } change:=FALSE; cust:=0; time:=0; for x := 0 to 9 do begin gueues[x]:=0; {инициализировать очереди } open[x]:=FALSE;{нет покупателей или очередей в начале дня } count[x]:=0; {счетчик очереди } end; OutTextXy(155, 190, '1 10'); OutTextXy(8, 190, 'Check-out lines; '); {теперь начинается ден. открытием первого пункта обслуживания } open[0]:=TRUE; repeat AddCust; AddQueue; Display; CheckOut; Display; if (time>30) and (time<50) then AddCust; if (time>70) and (time<80) then begin AddCust; AddCust; end; time:=time+1; until time>100; { конец дня } ReadLn; RestoreCrtMode; end.