Вычисление математических формул на PHP и Javascript

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

Решение мы будем искать на самых простых серверных и клиентских языках - PHP и Javascript.

 

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

Eval– позволяет исполнить произвольную строку. Зло этой функции заключается в том, что пользователь может в качестве строки указать много вредоносного кода и таким образом получить доступ, или обрушить что-нибудь на сервере. По этому – если решать задачу через eval – нужно лишь удостовериться что пользователь ввел допустимую строку.

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

Примерная формула может выглядеть следующим образом

function myfunc($x){ // пользовательская функция
    return $x/4;
}
$myvar=16; // пользовательская переменная
$expr='23.5 / 0+{myvar} *sqrt({myvar}/4)+myfunc({myvar})';

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

Деление на ноль допустимо в PHP, оно просто вернет 0. Остальные операторы будут суммироваться отдельно. На Javascript - деление на 0 выдаст бесконечность, но случай частный и не несет никакой смысловой нагрузки, главное чтобы программа не спотыкнулась, а продолжила свое выполнение.

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

Для реализации вышеследующего – перечислим все разрешенные конструкции. Только порядок замены должен идти от общего к частному. То есть сначала – наиболее общие выражения, например «asin(», затем «sin(» , затем «(» .

$test=strtr($expr,array(
    '{myvar}'=>'', // разрешенные переменные
    'myfunc('=>'', // разрешенные пользовательские функции
    '1'=>'',
    '2'=>'',
    '3'=>'',
    '4'=>'',
    '5'=>'',
    '6'=>'',
    '7'=>'',
    '8'=>'',
    '9'=>'',
    '0'=>'',
    '.'=>'',
    '+'=>'',
    '-'=>'',
    '*'=>'',
    '/'=>'',
    '%'=>'',
    'asin('=>'',
    'sin('=>'',
    'acos('=>'',
    'cos('=>'',
    'abs('=>'',
    'ceil('=>'',
    'exp('=>'',
    'floor('=>'',
    'tan('=>'',
    'round('=>'',
    'sqrt('=>'',
    ')'=>'',
    '('=>'',
    ' '=>'',
));
if ($test!='')die('Ошибка. Запрещенные символы '.$test);
if (substr_count($expr,'(')!=substr_count($expr,')'))die('Ошибка. Число открытых и закрытых скобок не совпадает');

Вот и все проверки. Остается только произвести замену нашей переменной и выполнить злую операцию вычисления

$expr=strtr($expr,array(
    '{inc}'=>'$inc',
    '++'=>'',
    '--'=>'',
));
eval('$result='.$expr.';');
echo $result;

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

Можно использовать это как угодно, загнать в функцию, в цикл. Для Javascript все аналогично.

Успехов в создании идеальных сервисов.

  • Автор: kosmom
  • Рейтинг: 0
  • Просмотров: 4014
  • Комментариев: 0
  • Создан: 28.05.2013 16:12

Комментарии (0)