Вычисления с не десятичными числами

Задача
Необходимо выполнить математические операции не над десятичными числами, а над восьмеричными или шестнадцатеричными. Например, определить корректные цвета веб-сайта в шестнадцатеричном формате.

Решение
Предваряйте число начальным символом, чтобы PHP смог узнать, что это не десятичное число. Следующие значения равны:

0144  // основание 8
100  // основание 10 
0x64  // основание 16

Ниже показан отсчет от 1 до 15 в шестнадцатеричной нотации:
for ($i = 0x1; $i < 0x10; $i++) { print "$i\n"; }


Обсуждение
Даже если в цикле for используются числа в шестнадцатеричном формате, по умолчанию все числа печатаются в десятичном формате. Другими словами, код из предыдущего раздела «Решение» не печатает «..., 8, 9, a, b, ...». Например:

for ($i = 0x1; $i < 0x10; $i++) { print dechex($i) . "\n"; }


Большинство вычислений проще выполнять в десятичной системе. Однако иногда логичнее переключиться на систему с другим основанием, например при использовании 216 веб-корректных цветов. Каждый код веб-цвета представляется в виде RRGGBB, где RR – это красный цвет, GG – зеленый цвет, а BB – голубой. Каждый цвет на самом деле представляет собой двузначное шестнадцатеричное число от 0 до FF.

Особыми веб-цвета делает то, что каждый из кодов RR, GG и BB должен быть одним из шести чисел: 00, 33, 66, 99, CC и FF (в десятичном формате: 0, 51, 102, 153, 204, 255). Поэтому 003366 – это веб-корректный цвет, а 112233 – нет. Веб-корректные цвета отображаются на 256-цветном мониторе без сглаживания переходов. В приведенном ниже тройном цикле числа создаваемого списка записываются в шестнадцатеричной системе, чтобы подчеркнуть шестнадцатеричную природу списка:

for ($rr = 0; $rr <= 0xFF; $rr += 0x33)
    for ($gg = 0; $gg <= 0xFF; $gg += 0x33)
        for ($bb = 0; $bb <= 0xFF; $bb += 0x33)
            printf("%02X%02X%02X\n", $rr, $gg, $bb);


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

Добавлено: 17 Июля 2018 07:41:17 Добавил: Андрей Ковальчук

Преобразование из одной системы счисления в другую

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

Решение
Обратитесь к функции base_convert():

$hex = 'a1';                  // шестнадцатеричное число (основание 16)
// преобразование из основания 16 в основание 10
$decimal = base_convert($hex, 16, 10); // переменная $decimal теперь равна 161


Обсуждение
Функция base_convert() изменяет строку в одной системе в соответствующую строку в другой системе. Она работает для всех систем с основаниями от 2 до 36 включительно. Для изображения чисел в системах с основанием больше 10 в качестве дополнительных символов используются буквы от a до z. Первый аргумент – это число, которое нужно преобразовать, за ним следует основание его системы, а в конце – основание ситемы, в которую требуется преобразовать число. Существует несколько специальных функций для прямого и обратного преобразования чисел в десятичную систему из других наиболее
востребованных систем с основаниями 2, 8 и 16. Это функции bindec() и decbin(), octdec() и decoct(),hexdec() и dechex():

// преобразование в десятичную систему
print bindec(11011); // 27
print octdec(33);    // 27
print hexdec('1b');  // 27
// преобразование из десятичной системы
print decbin(27);    // 11011
print decoct(27);    // 33
print dechex(27);    // 1b


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

Пусть требуется вывести на печать значения цветов HTML:

printf('#%02X%02X%02X', 0, 102, 204); // #0066CC

Добавлено: 17 Июля 2018 07:38:44 Добавил: Андрей Ковальчук

Работа с очень большими и очень маленькими числами

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

Решение
Для этого нужна либо библиотека BCMath, либо библиотека GMP. Применение BCMath:

$sum = bcadd('1234567812345678', '8765432187654321');
// переменная $sum равна теперь '9999999999999999'
print $sum;


Применение GMP:
$sum = gmp_add('1234567812345678', '8765432187654321');
// $sum теперь ресурс GMP, а не строка; для преобразования 
// используйте функцию gmp_strval()
print gmp_strval($sum);


Обсуждение
Библиотека BCMath проста в применении. Числа передаются как строки, а функция возвращает сумму (или разность, произведение и т. д.) в виде строки. Однако набор действий, которые можно производить над числами с помощью библиотеки BCMath, ограничен основными арифметическими операциями. Библиотека GMP доступна начиная с версии PHP 4.0.4. Большинство представителей семейства функций библиотеки GMP в качестве аргументов принимают целые и строки, но они преимущественно обмениваются числами в виде ресурсов, которые, по сути дела, представляют собой ссылки на числа.

Поэтому, в противоположность функциям BCMath, которые возвращают строки, функции GMP возвращают только ресурсы. Последние передаются затем любой функции GMP, которая работает с ними как с числами.Единственной оборотной стороной медали является то, что при работе с не-GMP функциями необходимо непосредственно конвертировать ресурсы с помощью функции gmp_strval() или функции gmp_intval(). Функции GMP либерально относятся к входным параметрам. Например:

$four = gmp_add(2, 2);            // Передаем целые
$eight = gmp_add('4', '4');       // Или строки
$twelve = gmp_add($four, $eight); // Или ресурсы GMP
print gmp_strval($twelve);        // Печатаем 12


Впрочем, с числами GMP можно совершать множество операций помимо сложения, таких как возведение в степень, быстрое вычисление больших факториалов, нахождение наибольшего общего делителя (НОД) и других:

// Возведение числа в степень
$pow = gmp_pow(2, 10);             // 1024
// Быстрое вычисление больших факториалов
$factorial = gmp_fact(20);         // 2432902008176640000
// Нахождение НОД
$gcd = gmp_gcd (123, 456);         // 3
// Другой нестандартный математический инструментарий
$legdendre = gmp_legendre(1, 7);   // 1


Библиотеки BCMath и GMP не обязательно доступны во всех конфигурациях PHP. Начиная с версии PHP 4.0.4 библиотека BCMath связана с PHP, поэтому она, вероятно, должна быть легко доступна. Однако если библиотека GMP не связана с PHP, то необходимо ее загрузить, инсталлировать и в процессе конфигурирования проинструктировать PHP об использовании этой библиотеки. Проверьте значения функций function_defined('bcadd') и function_defined('gmp_init') чтобы определить, можно ли использовать библиотеки BCMath и GMP.

Добавлено: 17 Июля 2018 07:37:57 Добавил: Андрей Ковальчук

Вычисление тригонометрических функций

Задача
Необходимо применить тригонометрические функции, такие как синус, косинус и тангенс.

Решение
В PHP реализованы тригонометрические функции sin(), cos() и tan():

$cos = cos(2.1232);


А также обратные им функции asin(), acos() и atan():

$atan = atan(1.2);


Обсуждение
Эти функции принимают аргументы в радианах, а не в градусах. Функция atan2() принимает две переменные $x and $y и вычисляет atan($x/$y). Однако она всегда возвращает правильный знак, поскольку определяет квадрант результата по значениям обоих параметров. Для секанса, косеканса и котангенса необходимо вручную вычислить обратные значения функций sin(), cos() и tan():

$n = .707;
$secant    = 1 / sin($n);
$cosecant  = 1 / cos($n);
$cotangent = 1 / tan($n);

Начиная с PHP 4.1 доступны гиперболические функции: sinh(), cosh() и tanh(), а также asin(), cosh() и atanh(). Однако обратные функции не поддерживаются в Windows.

Добавлено: 17 Июля 2018 07:36:07 Добавил: Андрей Ковальчук

Правильная печать слов во множественном числе

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

Решение
Это делается при помощи условного выражения:

$number = 4;
print "Your search returned $number " . ($number == 1 ? 'hit' : 'hits') . '.';
Your search returned 4 hits.


Обсуждение
Можно записать эту строку немного короче:

print "Your search returned $number hit" . ($number == 1 ? '' : 's') . '.';


Однако в других случаях образования множественного числа, таких как «person» → «people», очевидно, что надо изменить все слово, а не одну букву. Есть другой вариант – вызывать одну функцию для всех случаев образования множественного числа, как показано в функции pc_may_pluralize() из примера 2.2.

Пример 2.2. pc_may_pluralize()
function pc_may_pluralize($singular_word, $amount_of) {
    // массив особых слов во множественном числе
    $plurals = array(
        'fish' => 'fish',
        'person' => 'people',
    );
    // единственное значение
    if (1 == $amount_of) {
        return $singular_word;
    }
    // более одного, особая форма множественного числа
    if (isset($plurals[$singular_word])) {
        return $plurals[$singular_word];
    }
    // более одного, обычная форма множественного числа: 
    // добавить 's' в конце слова
    return $singular_word . 's';
}


Примеры:
$number_of_fish = 1;
print "I ate $number_of_fish " . pc_may_pluralize('fish', 
                                                  $number_of_fish) . '.';
$number_of_people = 4; 
print 'Soylent Green is ' . pc_may_pluralize('person', 
                                             $number_of_people) . '!';

I ate 1 fish.
Soylent Green is people!

Если в коде предполагается наличие нескольких слов во множественном числе, то нужна функция, облегчающая чтение, такая как pc_may_pluralize(). Этой функции передается слово в единственном числе в качестве первого аргумента и количество включений в качестве второго аргумента. В функцию включен большой массив, $plurals, содержащий все особые случаи. Если переменная $amount равна 1, то функция возвращает оригинальное слово. Если переменная больше единицы, то возвращается слово в особой форме множественного числа, если такая существует. По умолчанию добавляется только «s» в конце слова.

Добавлено: 17 Июля 2018 07:34:11 Добавил: Андрей Ковальчук

Форматирование чисел

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

Решение
Функция number_format() позволяет вывести число в формате целого:

$number = 1234.56;
print number_format($number);    // 1,235 поскольку число округлено

Определите число десятичных разрядов для форматирования в виде десятичной дроби:

print number_format($number, 2); // 1,234.56


Обсуждение
Функция number_format() форматирует число, вставляя необходимые разделители десятков и тысяч в соответствии с локализацией. Если требуется установить эти значения вручную, передайте их как третий и четвертый параметры:

$number = 1234.56;
print number_format($number, 2, '@', '#'); // 1#234@56


Третий аргумент выступает в качестве десятичной точки, а последний отделяет тысячи. Эти два аргумента необходимо указывать вместе. По умолчанию функция number_format() округляет число до ближайшего целого. Если надо сохранить все число, но заранее неизвестно, сколько разрядов будет после десятичной точки, прибегают к следующему приему:

$number = 1234.56; // ваше число
list($int, $dec) = explode('.', $number);
print number_format($number, strlen($dec));

Добавлено: 13 Июля 2018 08:45:11 Добавил: Андрей Ковальчук

Взятие логарифмов

Задача
Необходимо взять логарифм числа.

Решение
Для логарифмов по основанию e (натуральный логарифм) применяется функция log():

$log = log(10);          // 2.30258092994


Логарифмы по основанию 10 вычисляются при помощи функции log10():

$log10 = log10(10);      // 1


Для вычисления логарифмов по другим основаниям предназначена функция pc_logn():

function pc_logn($number, $base) {
    return log($number) / log($base);
}
$log2  = pc_logn(10, 2); // 3.3219280948874


Обсуждение
И функция log(), и функция log10() определены только для положительных чисел. В функции pc_logn() базовая формула изменена и логарифм числа по основанию n равен логарифму этого числа по произвольному основанию, поделенному на логарифм числа n по тому же самому основанию.

Добавлено: 13 Июля 2018 08:32:03 Добавил: Андрей Ковальчук

Генерация случайных чисел со смещением

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

Решение
Используйте функцию pc_rand_weighted(), показанную в примере 2.1.

// возвращает взвешенный случайно выбранный ключ
function pc_rand_weighted($numbers) {
    $total = 0;
    foreach ($numbers as $number => $weight) {
        $total += $weight;
        $distribution[$number] = $total;
    }
    $rand = mt_rand(0, $total - 1);
    foreach ($distribution as $number => $weights) {
        if ($rand < $weights) { return $number; }
    }
}


Обсуждение
Представьте, что вместо массива, значения элементов которого отражают количество оставшихся копий объявлений, есть массив объявлений, в котором каждое объявление встречается ровно столько раз, сколько осталось его копий. Можно просто указать на не взвешенное случайное место внутри массива, и это будет реклама для показа. Вместо этого можно определить величину возможного массива (путем подсчета остающихся копий), выбрать случайное число из диапазона размера воображаемого массива, а затем пробежаться по массиву, определяя, какое объявление соответствует выбранному числу.

Например:
$ads = array('ford' => 12234, // рекламодатель, остающиеся копии
             'att'  => 33424,
             'ibm'  => 16823);
$ad = pc_rand_weighted($ads);

Добавлено: 13 Июля 2018 08:31:16 Добавил: Андрей Ковальчук

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

Задача
Необходимо сгенерировать случайное число в пределах числового диапазона.

Решение
Для этого предназначена функция mt_rand():

// случайное число между $upper и $lower, включительно
$random_number = mt_rand($lower, $upper);


Обсуждение
Генерация случайных чисел полезна, когда надо вывести на экран случайную картинку, случайным образом назначить стартовую точку в игре, выбрать случайную запись из базы данных или сгенерировать уникальный идентификатор сессии. Для того чтобы сгенерировать случайное число в интервале между двумя точками, надо передать функции mt_rand() два аргумента:

$random_number = mt_rand(1, 100);


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

PHP имеет два различных генератора случайных чисел: классическую функцию под именем rand() и более совершенную функцию mt_rand(). MT (Mersenne Twister) – это генератор псевдослучайных чисел, названный в честь французского монаха и математика Марена Мерсенн (Marin Mersenne), исследовавшего простые числа. На этих простых числах и основан алгоритм данного генератора. Функция mt_rand() работает быстрее, чем функция rand(), и дает более случайные числа, поэтому мы отдаем предпочтение первой из них.

Если у вас версия PHP более ранняя, чем 4.2, то перед тем как первый раз вызвать функцию mt_rand() (или rand()), нужно инициализировать генератор начальным значением путем вызова функции mt_srand() (или srand()). Начальное значение – это число, которое случайная функция использует как основу для генерации возвращаемых ею случайных чисел; это относится к способу разрешения упомянутой выше дилеммы – повторяемость против случайности.

В качестве начального значения, меняющегося очень быстро и с малой вероятностью повторяемости (именно этими свойствами должно характеризоваться хорошее начальное значение), можно взять значение, возвращенное высокоточной функцией времени microtime(). Генератор достаточно инициализировать один раз. PHP 4.2 и более поздних версий автоматически управляет инициализацией, но если начальное значение устанавливается вручную перед первым вызовом функции mt_rand(), то PHP не заменяет его своим собственным начальным значением.

Если нужно выбрать случайную запись из базы данных, то проще всего сначала определить общее количество полей в таблице, выбрать случайное число из этого диапазона, а затем запросить эту строку из базы данных:

$sth = $dbh->query('SELECT COUNT(*) AS count FROM quotes');
if ($row = $sth->fetchRow()) {
    $count = $row[0];
} else {
    die ($row->getMessage());
}
$random = mt_rand(0, $count - 1);
$sth = $dbh->query("SELECT quote FROM quotes LIMIT $random,1");
while ($row = $sth->fetchRow()) {
    print $row[0] . "\n";
}


Этот фрагмент кода определяет общее количество строк в таблице, генерирует случайное число из этого диапазона, а затем использует LIMIT $random,1 для выбора (SELECT) одной строки из таблицы, начиная с позиции $random. В MySQL версии 3.23 или выше возможен альтернативный вариант:

$sth = $dbh->query('SELECT quote FROM quotes ORDER BY RAND() LIMIT 1');
while ($row = $sth->fetchRow()) {
    print $row[0] . "\n";
}


В этом случае MySQL сначала располагает строки в случайном порядке, а затем возвращает первую строку.

Добавлено: 13 Июля 2018 08:29:35 Добавил: Андрей Ковальчук

Работа с последовательностью целых чисел

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

Решение
Это делается при помощи функции range(), которая возвращает массив, состоящий из целых чисел:

foreach(range($start,$end) as $i) {
    plot_point($i);
}


Иногда вместо функции range() целесообразно применить цикл for. Для инкремента можно использовать также значения, отличные от 1.

Например:
for ($i = $start; $i <= $end; $i += $increment) {
    plot_point($i);
}


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

В первом примере функция range() возвращает массив значений от $start до $end. Затем foreach берет каждый элемент и присваивает его переменной $i внутри цикла. Преимущество применения функции range() в ее краткости, но этот инструмент имеет некоторые недостатки. Например, большой массив может занимать неоправданно большой объем памяти. Кроме того, приходится увеличивать ряд на одно число за раз, поэтому нельзя выполнить цикл, например для последовательности четных чисел.
Что касается PHP 4.1, то значение переменной $start может быть больше значения переменной $end. В этом случае функция range() возвращает числа в убывающем порядке. Также можно использовать итерацию для последовательности символов:

print_r(range('l', 'p'));
Array
(
[0] => l
     => m
     => n
     => o
     => p
)


Цикл for использует только единственное целое и совершенно не работает с массивом. Возможности цикла while богаче, он предоставляет больший контроль над циклом, так как позволяет увеличивать и уменьшать переменную $i более свободно. Можно изменять переменную $i внутри цикла, что не всегда можно сделать с функцией range(), поскольку PHP читает весь массив при входе в цикл, и изменения в массиве не оказывают влияния на последовательность элементов.

Добавлено: 13 Июля 2018 08:28:07 Добавил: Андрей Ковальчук

Округление чисел с плавающей точкой

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

Решение
Для того чтобы округлить число до ближайшего целого, предназначена функция round():

$number = round(2.4);   // $number = 2


Округление до ближайшего большего целого выполняется при помощи функции ceil():

$number = ceil(2.4);    // $number = 3


Функция floor() позволяет округлить число до ближайшего меньшегоцелого:

$number = floor(2.4);   // $number = 2


Обсуждение
Если число находится точно между двумя целыми, то поведение функции не определено:

$number = round(2.5);   // $number is 2 or 3!


Будьте осторожны! Мы упоминали в рецепте 2.2, что числа с плавающей точкой не всегда выражаются точным значением, поскольку это зависит от способа их внутреннего представления в компьютере. Это может создать ситуацию, когда очевидного ответа не существует. Вместо ожидаемого «0,5» значение может быть «,499999...9» (вся группа
состоит их девяток) или «,500000...1» (с многими нулями и завершающей единицей). Если вы хотите быть уверенным, что число округляется в большую сторону, добавьте небольшую дельту перед округлением:

$delta = 0.0000001;
$number = round(2.5 + $delta);   // $number = 3


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

$cart = 54.23;
$tax = $cart * .05;
$total = $cart + $tax;       // $total = 56.9415
$final = round($total, 2);   // $final = 56.94

Добавлено: 13 Июля 2018 08:27:21 Добавил: Андрей Ковальчук

Сравнение чисел с плавающей точкой

Задача
Необходимо проверить равенство двух чисел с плавающей точкой.

Решение
Задайте малую дельту и проверьте числа на равенство в пределах этой дельты:

$delta = 0.00001;
$a = 1.00000001;
$b = 1.00000000;
if (abs($a - $b) < $delta) { /* $a и $b равны */ }


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

Для того чтобы обойти эту трудность, вместо проверки равенства $a == $b следует обеспечить очень небольшую разность ($delta) между первым и вторым числом. Размер этой дельты должен быть меньше разницы между двумя числами, которую вы хотите обеспечить. Затем для получения абсолютного значения разности вызывается функция abs().

Добавлено: 13 Июля 2018 08:25:19 Добавил: Андрей Ковальчук