Статьи

Произвольная точность и большие числа в PHP

Математические вопросы и приложения всегда вызывают меня. Недавно я только что закончил книгу Стивена Хокинга « Бог сотворил целые числа », и возможность «поговорить» с этими великими математиками в истории вызывает у меня восхищение. Это главная причина, по которой я решил написать об этой теме.

В этой статье мы рассмотрим возможность PHP для вычисления чисел произвольной точности / вычисления больших целых чисел, рассмотрев 3 модуля PHP: GMP , BC Math и php-bignumbers . Мы продемонстрируем два реальных примера, чтобы увидеть возможности / ограничения каждого из них. Первым будет вычисление PI с произвольной точностью — ну, ради статьи, мы ограничим точность, скажем, до 1000 цифр; вторая будет простой демонстрацией шифрования / дешифрования RSA.

Давайте начнем.

подготовка

Чтобы получить модуль GMP PHP, просто выполните следующую команду в своем терминале в любой системе Unix (или установите его вручную, если в Windows — но, пожалуйста, используйте Vagrant !):

sudo apt-get установить php5-gmp

php-bignumbersComposer . Создайте файл composer.jsonphp composer.phar install Сайт php-bignumbers Github содержит подробную информацию о том, как написать файл composer.json

BC обычно предварительно установлен и включен.

После установки мы можем проверить, что все работает нормально, написав простой PHP-скрипт и запустив его в терминале:

 <?php

// Below is to test php-bignumber lib
require_once 'vendor/autoload.php'; // Required for autoload

use \Litipk\BigNumbers\Decimal as Decimal;

$one = Decimal::fromInteger(1);
$two= Decimal::fromInteger(2);
$three=Decimal::fromInteger(3);
$seven = Decimal::fromString('7.0');

$a=$one->div($seven, 100); // Should display something like 0.142857142857 .....and the last digit is 9 in consideration of rounding up the 101th digit. 
$b=$two->div($seven, 100);
$c=$three->div($seven, 100);

echo ($c->sub(($a->add($b)))); //Displays 0.00000... to the last digit

echo "\n";

// Now we test GMP

echo gmp_strval(gmp_div("1.0", "7.0")); //Displays 0. Not really useful!
echo "\n";

// Now we test BC

$oneseven=bcdiv('1', '7', 100); // Should display something like 0.142857142857 .....but note the last digit is 8, not 9.
$twoseven=bcdiv('2','7', 100);
$threeseven=bcdiv('3','7', 100);

echo bcsub(bcadd($oneseven, $twoseven,100), $threeseven, 100); // Displays 0.000000000... to the last digit
echo "\n";

Вышеприведенный код выведет как ниже:

В первом и третьем выходных данных (с использованием php-bignumbers и BC соответственно) они идентичны и точны (1/7 + 2 / 7-3 / 7 = 0).

Для GMP он обрабатывает только большие целые числа и не обеспечивает метод вычисления произвольных значений с плавающей запятой, поэтому он говорит 1/7 = 0, результат, который мы можем ожидать для одного целого числа, делящего другое. По этой причине я не буду использовать GMP для демонстрации произвольной точности (расчет PI), а буду использовать ее только в демонстрации RSA.

Краткое сравнение трех библиотек

PHP-bignumber:

  • Новый, в разработке;
  • Объектно-ориентированный;
  • Больше печатать, чтобы создать номер;
  • Ограниченные операторы и функции (на момент написания статьи отсутствует функция pow
  • Обеспечивает произвольную точность, что нам и нужно.

ДО НАШЕЙ ЭРЫ:

  • Зрелый, предустановленный и включенный;
  • Не объектно-ориентированный;
  • 10 доступных функций, пропуская все функции как php-bignumber, кроме pow
  • Обеспечивает произвольную точность, что нам и нужно.

GMP:

  • Зрелый, простой в установке;
  • Не объектно-ориентированный;
  • Доступно более 40 функций, но все они сосредоточены на целочисленных манипуляциях;
  • Обеспечивает произвольную точность для целых чисел, а не полностью то, что мы хотим.

Теоретически, при условии + — * /, фактически нет ограничений для моделирования других операций над числами. Может быть проще расширить php-bignumbers, чем расширить два других. Поэтому я с нетерпением жду, когда автор php-bignumbers добавит больше функций в будущем.

Если мы имеем дело только с целыми числами, и особенно с разделением (или, по крайней мере, как мы можем видеть в демонстрации RSA, нас больше интересует фактор и остаток), все 3 библиотеки являются хорошим кандидатом, и GMP может быть даже лучше, поскольку он обеспечивает наибольшее количество функций. Но если мы полны решимости получить плавающий результат с произвольной точностью, GMP не учитывается, и мы будем полагаться на BC и php-bignumbers.

Теперь давайте перейдем к демонстрации реального мира.

Расчет ИП

PI как иррациональное число, его значение можно вычислять бесконечно с любым количеством цифр. Расчет самого PI полезен при тестировании скорости ПК, а также при тестировании оптимизации алгоритма.

Поскольку этот расчет включает в себя плавающие числа, мы будем использовать BC и php-bignumbers. Для двух программ мы будем использовать самый простой способ вычисления PI:

π^2/6=1+1/4+1/9+......+1/(2n-1)^2

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

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

Наибольшее значение n Это позволит избежать слишком большого количества времени, затрачиваемого на завершение вычислений на моем ПК.

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

Я сделал некоторые основные оптимизации в расчете. Больше предложений по оптимизации приветствуются!

Код (и код RSA, показанный ниже) был загружен на Github .

Я проверил производительность (как с точки зрения скорости, так и точности) с разными значениями n

Откровенно говоря, этот результат действительно разочаровывает меня. При максимальном значении N, равном 2 миллионам, мы можем достичь точности только с точностью до шестого знака после запятой (3.141592) по сравнению с реальным PI. Но это связано со скоростью сходимости самого алгоритма.

Оба метода (BC и php-bignumbers) демонстрируют высокий уровень идентичности результатов: для 1000+ цифр они отличаются только после 990-й цифры. Это эквивалентно разнице 10E-990, которой достаточно для того, чтобы мы с одинаковым доверием относились к идентичности их выходов. Другими словами, при одинаковом алгоритме эти две библиотеки выдают «одинаковый» результат.

Однако скорости имеют огромный разрыв. Грубо говоря, BC занимает только 1/3 времени от числа php-номеров. Я полагаю, что это потому, что lib php-bignumbers тратит много времени на обработку, связанную с OO.

Обе библиотеки (а также GMP) внутренне используют строки для хранения чисел произвольной точности, формируя эту строку в указанные нами цифры. Манипуляции со строками также замедляют производительность. Также существует тот факт, что BC написан и выполняется на низком уровне — в C — тогда как bignumbers — это просто высокоуровневая PHP-библиотека.

Если мы снизим точность до 100 цифр, время будет значительно сокращено. Когда n = 999,999, 15 и 85 пройдут для BC и php-bignumbers, соответственно, и точность вывода все еще сохраняется. Результаты двух библиотек будут отличаться от 95-й цифры из 100+. Нам все еще удобно говорить, что они идентичны.

Таким образом, предложение на данном этапе BC все еще рекомендуется. Это обеспечивает ту же точность, но намного быстрее. Чтобы сэкономить время при выполнении вычислений произвольной точности, настоятельно рекомендуется установить меньшую шкалу точности. Время будет примерно 1/10, если масштаб 1/10 (1000 цифр стоят 128 с, а 100 цифр — 15 с для BC. Для php-номеров это всего 1/5).

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

Результат потрясающий. Когда n установлено в 999 999, встроенное плавающее число дает результат 3.1415916986586, почти идентичный тому, что мы можем получить из BC (3.1415916986595…). Но угадайте что? Это заканчивается почти мгновенно!

Таким образом, мой вывод таков: если мы не заботимся о цифрах, начиная с 10-й цифры и выше, нам, вероятно, вообще не следует использовать какие-либо произвольные библиотеки.

Демоверсия RSA

Раньше шифрование RSA пользовалось большим доверием, но в недавних новостях он освещался в центре внимания, обвинив возможное сотрудничество RSA с АНБ, внедрив черный ход в алгоритм шифрования RSA ( здесь ). Тем не менее, сам алгоритм все еще стоит обсудить здесь.

Подробное описание RSA выходит за рамки этой статьи. Заинтересованные стороны могут обратиться к его странице WIKI для подробного объяснения. Мы просто проиллюстрируем код, который пишем для реализации шифрования и дешифрования RSA.

Примечание . Код и иллюстрация шагов, представленные ниже, вдохновлены этой статьей , написанной на китайском языке китайским программистом г-ном Руаном.

Подготовительная работа к нашей демонстрации RSA показана ниже:

Мы не будем рассматривать алгоритм, который стоит за генерацией этих чисел. Пока нам просто нужно знать, что после этих шагов генерируется / выбирается 6 чисел: p, q, n, φ (n), e и d. Мы также знаем, что открытый ключ генерируется как (3233, 17), а закрытый ключ — (3233, 2753).

Шифрование числа (строки должны быть преобразованы в ASCII или Unicode символ за символом) выполняется в процессе, чтобы найти c

m^e = c (mod n), and m<n

То есть найти остаток от m^en

Допустим, мы хотели бы зашифровать букву «А»; его ASCII-код 65, тогда:

65^17 = 2790 (mod 3233)

Итак, c Мы можем отправить 2790 моему партнеру, и он будет использовать закрытый ключ (3233, 2753) для его расшифровки. (На практике мы получим открытый ключ от моего партнера, и он сохранит закрытый ключ при себе.)

Расшифровка будет похожа на решение следующего:

c^d=m (mod n)

То есть, чтобы узнать остаток ( mc^dn Таким образом,

2790^2753 = 65 (mod 3233)

Это оно! Мой друг расшифровал мое сообщение ( 279065

Программа PHP для этого процесса показана ниже. Я сначала использую BC, а затем GMP:

 <?php

$letter = 'A';

$m = ord($letter); // ASCII code of letter 'A'
$d = 2753;

$e = 17;
$n = 3233;

echo "Encryption / Decryption using BC:\n";
$c = bcmod(bcpow($m, $e, 40), $n);
$res = bcmod(bcpow($c, $d, 40), $n);

echo "Original data: $letter\n";
echo "Encrypted data: $c\n";
echo "Decryption:\n";
echo "Input: $c\n";
echo "Decrypted: $res\n";
echo "The letter is: ".chr($res)."\n";

echo "=========================\n";
echo "Encryption / Decryption using GMP:\n";
$c = gmp_powm($m, $e, $n);
$res = gmp_powm($c, $d, $n);

echo "Original data: $letter\n";
echo "Encrypted data:".gmp_strval($c)." \n";
echo "Decryption:\n";
echo "Input: ".gmp_strval($c)."\n";
echo "Decrypted: ".gmp_strval($res)."\n";
echo "The letter is: ".chr(gmp_strval($res))."\n";

Результаты показаны на снимке экрана ниже:

BC и GMP одинаково хорошо работают в этой демонстрации. Шифрование и дешифрование одинаково быстро и завершаются мгновенно. Поскольку алгоритм RSA включает только большие целые числа, точность хорошо сохраняется.

Здесь не показана возможность того, что обе библиотеки могут обрабатывать действительно большие числа. Например, в нашем процессе расшифровки мы вычислили очень большое число (2790 ^ 2753, которое является числом 9486 цифр)!

Разумеется, приведенная выше демонстрация не может быть использована в качестве серьезной программы RSA. Длина ключа (длина 3233, выраженная в двоичном виде) составляет всего 12. При фактическом использовании длина обычно должна быть установлена ​​равной 1024 цифрам или 2048 цифрам для особо важной информации.

Вывод

В этой статье мы рассмотрели 3 библиотеки, которые предоставляют возможности вычисления больших целых и / или произвольной точности: BC Math, GMP, php-bignumbers.

Рекомендация автора — использовать BC Math, так как он поддерживает как целые, так и плавающие числа, а также работает довольно быстро. Или мы можем полагаться на встроенные плавающие числа PHP, если нам нужны только первые 10 цифр или меньше.

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

Сообщение идет:

1486 1992 745 2185 2578 1313 1992 884 2185 1992 281 2185 2235 884 2412 3179 2570 2160 884 1313 1992 884 2185 1992 2680 3179 884 1313 612 2185 3179 2235 884 1853