Завершим начатый пример и напишем функции для численного решения уравнений. Начнем с выбора уравнения, которое мы будем исследовать ,и для которого сначала выведем график - чтобы проверить полученные корни как по доступным онлайн-калькуляторам, так и по пересечению графика с осью абсцисс.
Попробуем кубическое уравнение - формула Кардано достаточно громоздка, чтобы это уравнение не хотелось решать точным аналитическим методом, но она есть, а это значит, что у нас имеется критерий правильности решения.
Итак, само уравнение:
Для краткости запишем уравнение через "стрелочную" функцию. Помимо более краткой записи, такие функции "подхватывают" в свой контекст выполнения родительский объект, и изнутри этих функций доступен this родителя без трюков с предварительным присваиванием self = this.
Это важно при написании обработчиков событий, а также функций внутри reduce, map и forEach. Но еще это просто кратко. Заранее вычислим квадрат и куб переменной x, чтобы убрать одну лишнюю операцию умножения, которая возникает при "наивной" записи x*x*x и x*x.
А теперь - решение. Сначала мы опробуем метод дихотомии. Подробнее прочитать про него можно здесь (вики по основам машинного обучения).
Если объяснять "на пальцах", то суть метода в том, чтобы на участке, где функция проходит через ноль и меняет знак (и уравнение обладает корнем), каждый шаг сравнивать знаки функции в начале отрезка, середине и на конце. Если знаки в начале и середине совпадают, то переход через ноль не происходит, и мы рассматриваем диапазон между серединой и концом.
И наоборот. Каждый раз участок поиска уменьшается вдвое, и так до тех пор, пока размер отрезка не станет сопоставим с заранее заданной погрешностью поиска (или разница между значениями функции на отрезке).
Эти принципы реализованы в функции dichotomie. Она принимает на вход границы отрезка поиска x0_start и x1_start, указатель на исследуемую функцию func и величину поисковой погрешности eps.
Каждый шаг записывается в массив, элементы которого хранят границы отрезков x0 и x1, значение функции в центре отрезка поиска f_05 (перед разбиением пополам) и номер шага поиска. Стоит обратить внимание, что в массив передается имя переменной. В ES6 такая сокращенная запись позволяет передать в объект поле, имя которого соответствует имени переменной, а значение - значению переменной.
Чтобы иметь больше материала для сравнения, реализуем еще один метод численного поиска. Если объяснять "на пальцах", то мы проводим к графику касательную, смотрим, где касательная уходит в ноль, оттуда снова проходим касательную и так до схождения. Использование касательной подразумевает, что мы будем вычислять значение производной, а поскольку функции на входе могут быть произвольными, то мы будем использовать не аналитические формулы, а определение производной.
...Производная функции f(x) в точке x0 - это отношение приращения значения функции f(x0 + eps) - f(x0 ) к приращению аргумента x0 + eps - x0 ...
Теперь паззл почти собран. Мы можем рисовать графики функций, мы можем находить нули этих функций. Теперь решим уравнения, для проверки выведем графики функций, а результаты запишем в таблицу и вставим эту таблицу в html
Динамическое создание html можно выполнять множеством способов. Это и document.write (не надо так делать, плиз. Ну хотя для отладки - то why not), это и свойство innerHTML вместе со строковыми литералами, про которые я упоминал в предыдущем примере, или создание и вставка HTML узлов.
Переменная-узел создается методом createElement глобального объекта document. Созданным узлом можно манипулировать любыми способами, изменяя стили, внутреннее содержимое узла, классы. Затем узел можно вставить в другой узел html методом appendChild.
Поскольку в отличие от численных расчетов, где количество шагов может переваливать за десятки тысяч (с десятками операций на каждом шаге), при выводе на экран количество элементов вряд ли перевалит за сотню-другую. Здесь читаемость кода все-таки важнее быстродействия, и важно получить не только работоспособный, но и читаемый, чистый и поддерживаемый код.
Для этого у нас есть метод массивов forEach. Этот метод поглощает функцию-callback, а затем последовательно применяет ее ко всем элементам массива. Намного чище, нагляднее и лаконичнее, чем кондовый цикл for, пусть и ценой несколько меньшей производительности
Функция outputTable принимает на вход массив данных и таблицу с форматом вывода данных (содержит имя столбца и количество цифр после запятой, которые нужно выводить для этого столбца).
Метод последовательно создает таблицу, затем заголовок, содержащий имена столбцов, а потом поэлементным проходам выводит в каждую строку таблицы данные о шаге вычислений. Этими строками заполняется таблица, указатель на которую возвращается в результате. После этого достаточно прицелиться querySelector-ом в соответствующий узел документа и через appendChild вывести таблицу на документ. Вот так:
Заодно покажем график исследуемой кубической параболы:
Вот так выглядит текстовый вывод решения:
И
Если кому-то интересно порыться в недрах примера, то добро пожаловать сюда, в гитхаб. Надеюсь, что пример был интересным и познавательным.