Калькулятор кубических уравнений — пример использования MathJax для записи математических символов на сайте

27 мая в статье об интеграции библиотеки MathJax на «обычном» сайте и на сайте, работающем на WordPress я рассказал о том, как с ее помощью записывать формулы,  функции, выражения с использованием нормальных символов, принятых в математике, а не заниматься ухищрениями, пытаясь приспособить для этого обычный текст.

Простейший пример: сравните \( x = {q^2 \over 4} \sqrt {z^{1 \over 3}} \) и текстовое представление этого уравнения:
x = (q^2)/4 * sqrt(z^(1/3)) — на сразу и разберешься, что имеется ввиду. Читабельное отображение уравнения на странице сайта возможно именно благодаря MathJax (спасибо ее разработчикам).

В упомянутой статье я «грозился» 🧐 написать скрипт для решения кубических уравнений с использованием MathJax для вывода корней третьей степени (которые никак не написать доступными в HTML способами) и прочей «тяжелой» математики.

Это было сделано, калькулятор готов, вполне работоспособен 😀 и размещен на сайте — с ним можно ознакомиться и что-нибудь рассчитать на этой странице.

Для решения уравнений третьей степени \( ax^3 + bx^2 + cx + d = 0 \) в калькуляторе применяется универсальный метод Кардано, позволяющий решать любые уравнения данного типа. Метод сложный, стоит отметить, что в школе его не изучают )). Теоретическое обоснование этого метода также приведено на странице с калькулятором.

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

1. PHP и комплексные числа

Скрипт калькулятора написан на естественном для сайтостроения на языке PHP. PHP не умеет совершать операции с комплексными числами — это я, конечно, знал. А корни кубического уравнения могут в некоторых случаях быть комплексными, вида \( d + im \), где
d — действительное число,
коэффициент m — также действительное число,
i — мнимая единица, которая, как известно, определяется как \( i = \sqrt{-1} \).

Однако \( d \) и \( m \) вычисляются посредством хотя и сложных, но вполне доступных алгебраических операций с действительными числами, с использованием встроенных в PHP функций. Поэтому визуальное отображение мнимой части \( im \) корня уравнения реализуется просто — добавлением символа \( i \) к ранее рассчитанному значению \( m \). Примерно так:

<?php
...
$d = ... ; // Действительная часть корня - см. теоретическую часть

$m = ... : // Действительный коэффициент мнимой части корня

// Вывод корня уравнения (комплексного числа на монитор):

echo $d' . ' +i' . $m;
...
?>

Так что в этой части написание кода проблем не вызвало. Как видно, в данной, прикладной задаче комплексные числа легко «конструируются» из действительных, с добавлением символа \( i \).

2. PHP и кубические корни

При решении кубических уравнений приходится иметь дело с кубическими же корнями. Как их вычислять? В PHP имеется только встроенная функция для расчета квадратного корня sqrt($num), возвращающая квадратный корень из значения num. Для корней 3-й и высших степеней подобной функции нет.

Однако мы отлично помним со школьных лет ))), что \( \sqrt[n]{x} = x^ {1 \over n} \). Исходя из этого, корень кубический вычисляется по формуле \( \sqrt[3]{x} = x^ {1 \over 3} \), что в переводе на PHP «звучит» таким образом:

$cub_koren = pow($x, 1/3);

Все супер?! Вот и нет, в этом месте меня ожидал подвох. Как я уже писал, PHP не знает, что такое комплексные и, соответственно, мнимые числа. А переменная \( i \), кубический корень из которой мы собрались извлекать путем возведения в дробную степень 1/3, может принимать как положительные, так и отрицательные значения.

«Ну и в чем вопрос?» — скажут знатоки математики. Это ведь корень четной степени из отрицательного действительного числа — мнимое число, а корень нечетной степени из такого числа — всегда также действительное число, со знаком «минус».

Например, \( \sqrt[3]{-27} = -3 \).

Я рассуждал примерно так же, и мне в голову не приходило, что разработчики PHP не научили его возводить в дробную нечетную степень отрицательные числа — ведь в результате должно получиться «обычное» отрицательное число, относящееся к полю действительных (не комплексных, не мнимых) чисел! Каково же было мое удивление, когда в ответ на

$cub_koren = pow(-27, 1/3);
echo 'Корень кубический из -27 равен: ' . $cub_koren;

скрипт вывел на экран: Корень кубический из -27 равен: NAN

NAN в РНP — это сокращение от «Not a Number» (не число). Значение NAN возвращают математические функции в целом ряде случаев, например, когда не могут  корректно обработать исходные данные или считают операцию недопустимой.

Интересно, что возводить в нечетную степень отрицательные числа PHP вполне себе умеет, но только если показатель степени — целое число.

echo pow(-27, 3); // результат -19683.

Во всех остальных случаях получится NAN. Для меня странно, что PHP настолько ограничен в осуществлении не самой сложной математической операции… но это так, к слову. Возвращаюсь к калькулятору.

Извлечение кубического корня из отрицательного числа решилась просто, хотя и не без небольшого ухищрения. Как я уже писал, результат возведения отрицательного числа в нечетную степень (когда показатель степени — положительное число, хоть целое, хоть дробное) всегда также отрицательное число. Отсюда мораль:
если \( x < 0\),
то \( x^{1 \over 3} = -1\) · \( ((-1) \) · \( x)^{1 \over 3} \)

Иначе говоря, умножив заведомо отрицательную переменную \( x \) на минус единицу, мы «конвертировали» ее в положительное число (минус на минус дает плюс! 😀), которое возвели в степень 1/3 (что эквивалентно извлечению кубического корня), затем умножили результат также на минус единицу и, вуаля, получили искомое значение. И, все это на PHP:

...
if ($x < 0)
{$x = (-1)*$x;
$cub_koren = (-1)*pow($x, 1/3);}
...

3. Перенос строк в выражениях «MathJax»

Была еще одна сложность, выявленная при разработке калькулятора. Я считал важным, чтобы он не просто выдавал готовый результат, а также показывал промежуточные расчеты. Пользователь должен понимать, откуда что берется, иначе получится не подспорье в решении математических задач, а «шаманский бубен». Ну, или «черный ящик» )).

Однако при определенных значениях коэффициентов кубического уравнения эти расчеты оказались весьма громоздкими. Например, для уравнения 20001x³ + 3x² — 5x + 16 = 0 в процессе вычисления одного из корней на экран выводится такое выражение (на самом деле это лишь его небольшая часть):
\( = -{1 \over 2} (\sqrt[3] {- {-0.002497 \over 2} + \sqrt{ {(-0.002497)^2 \over 4} + {(-0.002499)^3 \over 27}}} + \)
\( + \sqrt[3] {- {-0.002497 \over 2} — \sqrt{ {(-0.002497)^2 \over 4} + {(-0.002499)^3 \over 27}}}) \)

Сейчас не буду объяснять, откуда взялись эти числа (подробности есть на странице калькулятора). Речь о переносе строк. Точнее о его отсутствии — в MathJax он не предусмотрен!! В некоторых статьях об этой «волшебной» библиотеке я читал, что разрыв строки был в более ранних ее версиях, за это отвечал оператор \\ — аналог <br /> в HTML. Но в последней на сегодняшней день версии (3.2.2) разработчики его почему-то не включили.

На обычных web-страницах перенос длинных строк происходит автоматически, это умеют делать все браузеры. Например, если написать длинное предложение, что-то вроде:
«Мама мыла раму, потом пришел папа, сказал что рама помыта плохо, потом пришла бабашка и наорала на папу, чтобы не лез не в свое дело, потом мышка прибежала, хвостиком махнула, яичко упало и разбилось, а потом они все дружно извлекали кубический корень из сферического коня в вакууме», —
никаких сложностей с визуализацией этой чепухи не возникнет — на на мониторе настольного ПК, ни на экране мобильного устройства. Вы, наверное, «проникнитесь» глубиной мысли автора 😅, но даже не задумаетесь, как браузер определил, после какого слова переносить следующее на новую строку, и почему эта галиматья не выстроилась в одну строку и не уехала за правую границу экрана.

С воспроизведением математических формул, написанных на языке разметки MathML, который считывает MathJax, такая «магия», увы, не работает. Автоматом строки не переносятся, принудительный разрыв строки, как уже было сказано, тоже не выполнить. Т.к. мы обсуждаем математику, можно сказать, что протяженность длинных выражений в MathJax стремится к плюс бесконечности по оси Х )).

Чтобы решить этот казус пришлось сочетать разметку MathML и обычный HTML. При этом нужно было учитывать, что на смартфоне в вертикальной ориентации размер экрана меньше, чем на настольном мониторе. Для этого пришлось писать 2 варианта кода, отдельно для «мобильников» и для обычных компьютеров (так, если записать выражение из приведенного выше примера с корнями не в две, а в одну строку, оно не уместится на экране смартфона, и пользователь не увидит его окончание).

В обоих вариантах перенос строки осуществляется одинаково: путем разбивки выражения для MathJax на несколько частей (каждый «кусок» для новой строки) и добавлением в конце каждой из них <br />. Посмотрите пример:

\( x + y + z = \) <br />  
\( = a +b + c \)

При такой разметке MathJax выведет на экран:
\( x + y + z = \)
\(= a +b + c \)

Добавлю, что при написании мобильной части кода пришлось также:
— насколько возможно без ущерба для читабельности, уменьшить размер шрифта выводимых MathJax выражений (это достигается добавлением <span style=“font-size: 0.8em”></span> — они вполне восприимчивы к css-стилям);
— в сократить в числах количество знаков до шести после запятой (она же «плавающая точка») в промежуточных расчетах (в подкоренных выражениях, которые по правилам математики нельзя переносить на другую строку), иначе они все равно получались слишком длинными.

Ограничение дробной части чисел до 6 символов было выполнено так:

$q = substr($q, 0, strrpos($q, '.') + 7);  
// Обрезает значение $q вида 1.123456789101112... до 1.123456

4. Экспоненциальное представление больших и малых величин в PHP

Сразу начну с примера. Для расчета корней уравнения 2001x³ + 3x² — 5x — 5 = 0 оно вначале путем определенной подстановки приводится к виду y³ + py + q = 0, для которого по специальной формуле рассчитывается дискриминант (величина, необходимая для дальнейших вычислений).

Для указанного случая калькулятор выдал дискриминант в следующей форме:
\( D = 1.5588002341629E-6 \)

Это ни что иное как 1.5588002341629 х 10-6 или 0.0000015588002341629.

Я согласен с уважаемыми разработчиками PHP, что числа с многими нулями не есть хорошо для их визуального восприятия, но зачем превращать понятное большинству 10-6 в странное «E-6»?.. Такое решение, судя по обсуждениям на различных форумах, не нравится многим. Немного покопался в этой теме — чаще всего web-мастера спрашивают, как перевести число с «Е» из экспоненциальной формы в десятичную дробь с плавающей точкой.

Мне решение такого рода не подходило. Как уже было отмечено, слишком длинные числа для калькулятора — только помеха.

Была мысль добавить в скрипт функцию, которая преобразовывала бы для наглядности числа вида “xЕn” в “x·10n”. Однако делать этого пока не стал — не уверен, что это такая уж необходимая вещь, скорее, украшательство. Вместо этого добавил примечание с кратким разъяснением, что такое числа с добавлением «Е», которое выводится на экран при срабатывании условия, что хотя бы одну из рассчитанных величин калькулятор выдает в экспоненциальном формате. При отсутствии таких чисел примечание не появляется.

Для проверки наличия «Е» использована функция PHP strstr, которая проверяет вхождение в строку искомой подстроки:

...
if (strstr($D, E) || strstr($p, E) || ... )
echo 'Примечание: ... ';
...
Поделиться:

Добавить комментарий