Приглашаем посетить
Древнерусская литература (drevne-rus-lit.niv.ru)

2.9. Повышение фактора случайности

Назад
Глава 2 Числа
Вперед

2.9. Повышение фактора случайности

Проблема

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

Решение

Воспользуйтесь другими генераторами случайных чисел - например, теми, которые присутствуют в модулях Math::Random и Math::TrulyRandom с CPAN:
use Math::TrulyRandom;
$random = truly_random_value();
use Math_Random;
$random = random_uniforni();

Комментарий

Для генерации случайных чисел в Peri используется стандартная библиотечная функция С rand(3) (впрочем, на стадии компоновки это можно изменить). Некоторые реализации функции rand возвращают только 16-разрядные случайные числа или используют слабые алгоритмы, не обеспечивающие достаточной степени случайности. Модуль Math::TrulyRandom генерирует случайные числа, используя погрешности системного таймера. Процесс занимает некоторое время, поэтому им не стоит пользоваться для генерации большого количества случайных чисел. Модуль Math::Random генерирует случайные числа с помощью библиотеки randlib. Кроме того, он содержит многочисленные вспомогательные функции.

Смотри также: Описание функций srand и rand в perlfunc(1); рецепты 2.7-2.8; документация по модулям Math::Random и Math::TrulyRandom с CPAN.

2.10. Генерация случайных чисел с неравномерным распределением

Проблема

Требуется генерировать случайные числа в ситуации, когда одни значения появляются с большей вероятностью, чем другие (неравномерное распределение). Допустим, вы отображаете на своей Web-странице случайный баннер и у вас имеется набор весовых коэффициентов, определяющих частоту появления того или иного баннера. А может быть, вы имитируете нормальное распределение (закон распределения Гаусса).

Решение

Если вам потребовались случайные величины, распределенные по конкретному закону (допустим, по закону Гаусса), загляните в учебник по статистике и найдите в нем нужную функцию или алгоритм. Следующая функция генерирует случайные числа с нормальным распределением, со стандартным отклонением 1 и нулевым математическим ожиданием.
sub gaussian_rand {
my ($u1, $u2); # Случайные числа с однородным распределением
my $w; #Отклонение, затем весовой коэффициент
my ($g1, $д2); # Числа с гауссовским распределением
do {
$u1 = 2 * rand() - 1;
$u2 = 2 * rand() - 1;
$w = $u1*$u1 + $u2*u2 } while ($w >= 1);
$w = sqrt( (-2 * log($w)) / $w);
$g2 = $u1 * $w;
$.g1 = $u2 * $w;
# Возвратить оба числа или только одно return wantarray ? ($g1, $g2) : $g1;
}
Если у вас есть список весовых коэффициентов и значений и вы хотите выбирать элементы списка случайным образом, выполните два последовательных шага. Сначала превратите весовые коэффициенты в вероятностное распределение с помощью приведенной ниже функции weight_to_dist, а затем воспользуйтесь функцией weighted_rand для случайного выбора чисел.
# weight_to_dist: получает хэш весовых коэффициентов
# и возвращает хэш вероятностей
sub weight_to_dist {
my %weights = @_;
my %dist =();
my $total = 0;
my ($key, $weight);
local $_;
foreach (values %weights) { $total += $ ;
while ( ($key, $weight) = each %weights ) { $dist{$key} = $weight/$total;
}
return %dist;
}
# weighted_ran: получает хэш вероятностей
# и возвращает случайный элемент хэша

sub weighted_rand {
my %dist = @_; my ($key, $wp1rihn'
while (1) { # Чтобы избежать погрешностей вычислений
# с плавающей запятой (см. ниже). my $rand = rand;
while ( ($key, $weight) = each %dist ) { return $key if ($rand -= $weight) <0;
}
}
}

Комментарий

Функция gaussian_rand реализует полярный метод Бокса-Мюллера для преобразования двух независимых случайных чисел с однородным распределением, лежащих в интервале от 0 до 1 в два числа с математическим ожиданием 0 и стандартным отклонением 1 (то есть распределенных по закону Гаусса). Чтобы сгенерировать числа с другим математическим ожиданием и стандартным отклонением, умножьте выходные данные gaussian_rand на нужное стандартное отклонение и прибавьте математическое ожидание:
# gaussian_rand - см. выше $mean = 25;
$sdev = 2;
$salary - gaussian_rand() * $sdev + $mean;
printf("You have been hired at \$%.2f\n", $salary);
Функция weighted_rand получает случайное число из интервала от 0 до 1. Затем она использует вероятности, сгенерированные weight_to_dist, и определяет, какому элементу соответствует это случайное число. Из-за погрешностей представления с плавающей запятой накопленные ошибки могут привести к тому, что возвращаемый элемент не будет найден. Поэтому код размещается в цикле while, который в случае неудачи выбирает новое случайное число и делает очередную попытку. >Кроме того, модуль Math::Random с CPAN содержит функции, генерирующие случайные числа для многих распределений.

Смотри также: Описание функции rand в perlfunc(1); рецепт 2.7; документация по модулю Math::Random с CPAN.

2.11. Выполнение тригонометрических вычислений в градусах

Проблема

Требуется, чтобы в тригонометрических функциях использовались градусы вместо стандартных для Peri радианов.

Решение

Создайте функции для преобразований между градусами и радианами (2л радиан соответствуют 360 градусам).
BEGIN {
use constant PI => 3.14159265358979;
sub deg2rad {
my $degrees = shift;
return ($degrees / 180) * PI;
}
sub rad2deg {
my $radians = shift;
return ($radians / PI) * 180;
}
}
Также можно воспользоваться модулем Math::Trig:
use Math::Trig;
$raaians = deg2raa(,iioegreesj;
$degrees = rad2deg($radians);

Комментарий

Если вам приходится выполнять большое количество тригонометрических вычислений, подумайте об использовании стандартных модулей Math::Trig или POSIX. В них присутствуют дополнительные тригонометрические функции, которых нет в стандартном Peri. Другой выход заключается в определении приведенных выше функций rad2deg и deg2rad. В Peri нет встроенной константы я, однако при необходимости ее можно вычислить настолько точно, насколько позволит ваше оборудование для вычислений с плавающей запятой. В приведенном выше решении л является константой, определяемой командой use constant. Синус угла, заданного в градусах, вычисляется следующим образом: # Функции deg2rad и rad2def приведены выше или взяты из Math::Trig sub degree_sine {
my $degrees = shift;
my $radians = deg2rad($degrees);
my $result= sin($radians);
return $result;
}


Смотри также: Описание функций sin, cos и atan2 в perlfunc(1); стандартная документация по модулям POSIX и Math::Trig.

2.12. Тригонометрические функции

Проблема

Требуется вычислить значения различных тригонометрических функций - таких как синус, тангенс или арккосинус.

Решение

В Peri существуют лишь стандартные тригонометрические функции sin, cos и atan2. С их помощью можно вычислить тангенс (tan) и другие тригонометрические функции:
sub tan {
my $theta = shift;
return sin($theta)/cos($theta^:
}
В модуле POSIX представлен расширенный набор тригонометрических функций:
use POSIX;
$у = acos(3.7);

Модуль Math::Trig содержит полный набор тригонометрических функций, а также позволяет выполнять операции с комплексными аргументами (или дающие комплексный результат):
use Math::Trig;
$у = acos(3.7):

Комментарий

Если значение $theta равно я/2, Зл/2 и т. д., в функции tan возникает исключительная ситуация деления на ноль, поскольку для этих углов косинус равен нулю. Аналогичные ошибки возникают и во многих функциях модуля Math::Trig. Чтобы перехватить их, воспользуйтесь конструкцией eval:
eval {
$y = tan($pi/2);
} or return undef;


Смотри также: Описание функций sin, cos и atan2 в perlfunc(i). Тригонометрия в контексте комплексных чисел рассматривается в рецепте 2.15, а использование eval для перехвата исключений - в рецепте 10.12.


Назад
Вперед