Приглашаем посетить
Никитин (nikitin.lit-info.ru)

2.13. Вычисление логарифмов

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

2.13. Вычисление логарифмов

Проблема

Требуется вычислить логарифм по различным основаниям.

Решение

Для натуральных логарифмов (по основанию е) существует встроенная функция log:
$log_e = log(VALUE);
Чтобы вычислить логарифм по основанию 10, воспользуйтесь функцией log 10 модуля POSIX:
use POSIX qw(log-IO);
$log_10 = log10(VALUE);

Для других оснований следует использовать соотношение:
loge(x) = loge(x)/loge(n)
где х - число, логарифм которого вычисляется, n - нужное основание, а е - основание натуральных логарифмов.
sub log_base {
my ($base, $value) = @_;
return log($value)/log($base):
}

Комментарий

Функция log_base позволяет вычислять логарифмы по любому основанию. Если основание заранее известно, намного эффективнее вычислить его натуральный логарифм заранее и сохранить для последующего использования, вместо того чтобы каждый раз пересчитывать его заново. Я Определение log_base см. выше
$answer = log_base(10, 10, 10_000);
print "log10(10,100) = $answer\n";
log10(10,000) = 4

В модуле Math::Complex для вычисления логарифмов по произвольному основанию существует функция 1одп(), поэтому вы можете написать:
use Math;:Complex;
printf "lоg2(1024) = %lf\n", 1оgn(1024, 2); # Обратите внимание # на порядок аргументов!
1оg2(1024) = 10,000000
хотя комплексные числа в вычислениях не используются. Функция не очень эффективна, однако в будущем планируется переписать Math::Complex на С для повышения быстроты.

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

2.14. Умножение матриц

Проблема

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

Решение

Воспользуйтесь модулями PDL с CPAN. Модули PDL (Peri Data Language, то есть "язык данных Peri") содержат быстрые и компактные матричные и математические функции:
use PDL;
# $а и $b - объекты pdl $с = $а * $Ь;

Альтернативный вариант - самостоятельно реализовать алгоритм умножения матриц для двумерных массивов:
sub mmult {
my ($m1,$m2) = @>_;
my ($m1rows,$m1cols) = matdim($m1);
my ($m2rows,$m2cols) = matdim($m2);
unless ($m1cols == $m2rows) { # Инициировать исключение
die "IndexError: matrices don't match: $m1cols != $m2rows";
}
my $result = [];
my ($i, $j, $k);
for $i (range($m1rows)) { for $j (range($m2cols)) { for $k (range($m1cols)) {
$result->[$i][$j] += $m1->[$i][$k] * $m2->[$k][$j];
}
}
} return $result;
}
sub range {0 .. ($_[0] - 1 }
sub veclen { o
my $ary_ref = $_[0];
my type = ref $ary_ref;
if ($type ne "ARRAY") {die "$type is bad array ref for $ary_ref return scalar(@$ary_ref);
}
sub matdim {
my $matrix = $_[0], my o$rows = veclen($matrix);
my $cols = veclen($matrix->[0]);
return ($rows, $cols):
}

Комментарий

Если у вас установлена библиотека PDL, вы можете воспользоваться ее молниеносными числовыми операциями. Они требуют значительно меньше памяти и ресурсов процессора, чем стандартные операции с массивами Peri. При использовании объектов PDL многие числовые операторы (например, + и *) перегружаются и работают с конкретными типами операндов (например, оператор * выполняет так называемое скалярное умножение). Для умножения матриц используется перегруженный оператор х.
use PDL;
$а = pdl [
[ 3, 2, 3 ], [ 5, 9, 8 ], ];
$b = pdl [ [ 4, 7 ], [ 9, 3 ], [ 8, 1 ],
}
$c = $a x $b; # Перегруженный оператор х
Если библиотека PDL недоступна или вы не хотите привлекать ее для столь тривиальной задачи, матрицы всегда можно перемножить вручную: и mmultO и другие процедуры определены выше
$х = [
[ 3, 2, 3 ],
[ 5, 9, 8 ], ];
$У = l
[ 4, 7 ], [ 9, 3 ], [ 8, 1 ],
];
$z = mult($x, $y):


Смотри также: Документация по модулю PDL с CPAN.

2.15. Операции с комплексными числами

Проблема

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

Решение

Либо самостоятельно организуйте хранение вещественной и мнимой составляющих комплексного числа, либо воспользуйтесь классом Math::Complex (из стандартной поставки Peri). Ручное умножение комплексных чисел
# $с = $а * $b - моделирование операции $с_геа1 = ( $а_геа1 " $b_real ) - ($a_imaglnary * $b_imaginary );
$c_imaginary = ( $а_геа1 * $b_lmaginary ) - ($b_real * $a_imaginary );
Math::Complex
# Умножение комплексных чисел с помощью Math::Complex
use Math::Complex;
$с = $а * $b;

Комментарий

Ручное умножение комплексных числа 3+5i и 2-2i выполняется следующим образом:
$а_геа1 = 3; $a_imaginary =5; # 3 + 5i;
$b_real = 2; $b_lmaginary = -2; # 2 - 2i;
$c_real = ($a_real * $b_real ) - ( $a_imaginary * $b_imaginary );
$c_imaginary = ($a_real * $b_imaginary ) - ( $b_real * $a_imaginary );
print "с = ${c_real}+${c_imaginary}i\n";
с = 16+41

То же с применением модуля Math::Complex:

use Math::Complex;
$a = math::complex->new(3,5);
$b = math::complex->new(2,-2);
$c = $a * $b;
print "c = $c\n":
с = 16+4i
Версия 5.004 позволяет создавать комплексные числа с помощью конструкции cplx или экспортированной константы 1:
use Math::Complex;
$c = cplx(3,5) * ср1х(2,-2); # Лучше воспринимается $d = 3 + 4*i: ff 3 + 4i printf "sqrt($d) = %s\n", sqrt($d);
sqrt(3+4i) = 2+i
В исходном варианте модуля Math::Complex, распространяемом с версией 5.003, не перегружаются многие функции и операторы версии 5.004. Кроме того, Math::Complex используется модулем Math::Trig (появившимся в версии 5.004), поскольку некоторые функции могут выходить за пределы вещественной оси в комплексную плоскость - например, арксинус 2.

Смотри также: Документация по стандартному модулю Math::Complex.

2.16. Преобразования восьмеричных и шестнадцатеричных чисел

Проблема

Требуется преобразовать строку с восьмеричным или шестнадцатеричным представлением (например, "0х55" или "0755") в правильное число. Peri воспринимает лишь те восьмеричные и шестнадцатеричные числа, которые встречаются в программе в виде литералов. Если числа были получены при чтении из файла или переданы в качестве аргументов командной строки, автоматическое преобразование не выполняется.

Решение

Воспользуйтесь функциями Peri oct и hex:

$number = hex($hexadecimal); # Шестнадцатеричное число
$number = oct($octal);
# Восьмеричное число

Комментарий

Функция oct преобразует восьмеричные числа как с начальными нулями, так и без них ("0350" и "350"). Более того, она даже преобразует шестнадцатеричные числа, если у них имеется префикс "Ох". Функция hex преобразует только шестнадцатеричные числа с префиксом "Ох" или без него - например, "0х255", "ЗА", "ff" или "deadbeef" (допускаются символы верхнего и нижнего регистров). Следующий пример получает число в десятичной, восьмеричной или шестна-дцатеричной системе счисления и выводит его во всех трех системах счисления. Для преобразования из восьмеричной системы используется функция oct. Если введенное число начинается с 0, применяется функция hex. Затем функция printf при выводе преобразует число в десятичную, восьмеричную и шестнадцатерич-ную систему:

print "Gimme a number in decimal, octal, or hex: ";
$num = :
chomp $num;
exit unless defined $num;
$num = oct($num) is $num =~ /"О/;
# Обрабатывает как восьмеричные,
# так и шестнадцатеричные числа printf "%d %x %o\n", $num, $num, $num;

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

print "Enter file permission in octal: ";
$permissions = ;
die "Exiting ...\n" unless defined $permissions;
chomp $permissions;
$permissions = oct($permissions);
# Режим доступа всегда задается
# в восьмеричной системе print "The decimal value is $permissions\n";

Смотри также: Раздел "Scalar Value Constructors" в perldata(1) описание функций oct и hex в perlfunc(1).

2.17. Вывод запятых в числах

Проблема

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

Решение

Обратите строку, чтобы перебирать символы в обратном порядке, - это позволит избежать подстановок в дробной части числа. Затем воспользуйтесь регулярным выражением, найдите позиции для запятых и вставьте их с помощью подстановки. Наконец, верните строку к исходному порядку символов.
sub commify {
my $text = reverse $_[0];
$text =~ s/(\d\d\d)(?=\d)(^!\d*\.)/$1./g:
return scalar reverse $text;
}

Комментарий

Регулярные выражения намного удобнее использовать в прямом, а не в обратном направлении. Учитывая этот факт, мы меняем порядок символов в строке н;> противоположный и вносим небольшие изменения в алгоритм, который многократно вставляет запятые через каждые три символа от конца. Когда все вставки будут выполнены, порядок символов снова меняется, а строка возвращается из функции. Поскольку функция reverse учитывает косвенный контекст возврата, мы принудительно переводим ее в скалярный контекст. Функцию нетрудно модифицировать так, чтобы вместо запятых разряды разделялись точками, как принято в некоторых странах. Пример использования функции commify выглядит так: и Достоверный счетчик обращений :-)

use Math::TrulyRandom;
$hits = truly_random_value(); # Отрицательное значение!
$output = "your web page received $hits accesses last month.\n";
print commify($output);
Your web page received -1,740,525,205 accesses last month.


Смотри также: perllocale(1); описание функции reverse в perlfunc(1).

2.18. Правильный вывод во множественном числе

Проблема

Требуется вывести фразу типа: "It took $time hours" ("Это заняло $time часов"). Однако фраза "It took I hours" ("Это заняло 1 часов") не соответствует правилам грамматики. Необходимо исправить ситуацию'.

Решение

Воспользуйтесь printf и тернарным оператором X?Y:Z, чтобы изменить глагол или существительное. К сожалению, для русского языка этот рецепт не подойдет, поскольку множественное число в нем образуется по более сложным правилам с большим количеством исключений. - Примеч. перев.

printf "It took %d hour%s\n", $time, $time == 1 9 "" : "s";
printf "%d hour%s %s enough.\n", $time, $time == 1 ? "" : "s";
$time == 1 ? "is".: "are";


Кроме того, можно воспользоваться модулем Lingua::EN::Inflect с CPAN, упоминаемым в комментарии.

Комментарий

Невразумительные сообщения вроде "1 file(s) updated" встречаются только из-за того, что автору программы лень проверить, равен ли счетчик 1. Если образование множественного числа не сводится к простому добавлению суффикса s, измените функцию printf соответствующим образом:

printf "It took %d centur%s", $time, $time == 1 ? "у" : "ies";

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

sub noun_plural {
local $_ = shift;
# Порядок проверок крайне важен!
s/ss$/sses/
s/([psc]h)$/${1}es/
s/z$/zes/
s/#/$/ffs/
s/f$/ves/
s/ey$/eys/
s/y$/ies/
s/ix$/ices/
s/([sx])$/$1es/
s/$/s/
die "can't get here";
return $_;
} *verb_singular = \&noun_plural; # Синоним функции

Однако со временем будут находиться новые исключения и функция будет становиться все сложнее и сложнее. Если у вас возникнет потребность в подобных морфологических изменениях, воспользуйтесь универсальным решением, которое предлагает модуль Lmgua:EN::Inflect от CPAN.
use Lingua::EN::Inflect qw(PL classical);
classical 1); # Почему не сделать по умолчанию?
while () { # Каждая строка данных
for (split) { # Каждое слово в строке
print "One $_, two ", PL($_), ".\n";
}
}

# И еще один вариант
$_ = 'secretary general';
print "One $_, two ", PL($_), ".\n";
__END__ fish fly ox
species genus jockey index matrix mythos phenomenon formula
Результат выглядит так:
One fish, two fish.
One fly, two flies.
One ox, two oxen.
One species, two species.
One genus, two genera.
One phylum, two phyla,
One cherub, two cherubim.
One radius, two radii.
One jockey, two jockeys.
One index, two indices,
One matrix, two matrices.
One mythos, two mythoi.
One phenomenon, two phenomena.
One formula, two formulae.
One secretary general, two secretaries general.


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

Смотри также: Описание тернарного оператора выбора в реrlор(1); документация по модулю Lingua::EN::Inflect с СРАМ.


Назад
Вперед