математические задачи

Математические задачи

Язык JavaScript пригоден не только для управления Web-страницами, но и для решения разного рода расчетных и математических задач, иногда встречающихся при разработке сайтов. С другой стороны, составление или анализ подобных кодов — хорошее упражнение в программировании.

Здесь предлагаются простые варианты решенния следующих задач:

Решение квадратного уравнения

Напомню формулировку задачи. Требуется найти два числа x1 и x2 такие, чтобы при подстановке любого из них в выражение ax2 + bx + c оно было бы равно нулю. Такие x1 и x2 называются корнями уравнения ax2 + bx + c = 0. Все сказанное относится к математической постановке задачи. На первый взгляд может показаться, что для решения задачи о квадратном уравнении следует просто вспомнить или найти в справочнике подходящую формулу. И действительно, это сделать нетрудно. Однако в программах для решения задач такого рода часто значительную долю кода занимают различные проверки и анализ исходных данных. Организация же вычислений по конечным формулам из справочника — не самое сложное. Так, в рассматриваемой задаче в зависимости от значений коэффициентов a, b и c возможны различные частные случаи.
Корни могут быть комплексными и действительными. Допустим, нас интересуют только действительные корни. Тогда корней может и не быть совсем, или существовать только один, или два, одинаковые или различные. Таким образом, прежде чем мы дойдем до применения формулы, придется проанализировать, с каким именно случаем имеем дело. Очевидно, функция (назовем ее beq()) для вычисления действительных корней квадратного уравнения должна принимать три параметра — коэффициенты a, b и c. Возвращаемым значением будет массив. Если корней нет, то этот массив сделаем пустым. В противном случае в нем будет два элемента. Если уравнение имеет два корня, то запишем их в качестве элементов возвращаемого массива. Если корень единственный, то он будет записан в первом элементе массива, а второй элемент будет пустым (null).
Вот один из возможных вариантов определения функции:
function beq(a, b, c){   // решение квадратного уравнения
 var aret = new Array();
 var D = b*b — 4*a*c;
 if (a == 0) {
  if (!(b == 0)){
   aret[0]=-c/b;
   aret[1]=null;
  }
  return aret   // корней нет или корень единственный
 }
 if (D == 0) {   // одинаковые корни
  aret[0]= -b/2/a;
  aret[1]= aret[0];
 }
 if (D>0){     // различные корни
  aret[0]=(-b - Math.sqrt(D))/2/a;
  aret[1]=(-b + Math.sqrt(D))/2/a;
 }
 return aret
}
Проверим работу функции на нескольких наборах исходных данных:
beq(0, 2, 6);   // массив [-3, null]
beq(1, -2, 1);  // массив [1, 1]
beq(3, 4, -2.5); // массив [-1.797054997187545, 0.4637216638542114]
beq(2, 0, 5);   // пустой массив

Вычисление интеграла

Интеграл от некоторого выражения f(x) с одной переменной x на интервале ее значений от a до b можно понимать как площадь, ограниченную кривой y = f(x), осью абсцисс x и двумя прямыми, параллельными оси ординат y и проходящими через точки a и b на оси x. Аналогия с площадью справедлива лишь отчасти. Если указать, что площадь под кривой, проходящей ниже оси абсцисс, отрицательная, тогда все в порядке. Итак, для вычисления интеграла от выражения требуется определить площадь некоторой замысловатой фигуры. Алгоритм решения этой задачи основан на простой идее: разбить сложную фигуру на множество простых, площадь которых легко рассчитать, а затем просуммировать эти площади. Здесь возникает вопрос о точности такого решения. Однако легко заметить, что чем меньше элементарные фигуры и чем больше их количество, тем ближе результат к действительной площади исходной фигуры. Это так называемая задача приближения или аппроксимации. В качестве элементарной фигуры можно взять прямоугольник или трапецию, площади которых легко вычислить. Однако трапеция лучше, чем прямоугольник вписывается в кривую, поэтому выберем именно ее. Напомню: чтобы найти площадь трапеции, необходимо умножить полусумму ее параллельных сторон на высоту. Для получения множества таких трапеций, требуется разбить участок ab оси абсцисс на элементарные отрезки. В математике все идеально: отрезок ab разбивается на элементарные части, длина которых стремится к нулю, т. е. является бесконечно малой величиной. На практике мы имеем дело с конечными величинами. Более того, мы должны учесть, что с уменьшением длины элементарных отрезков и увеличением их числа возрастает время вычислений. Таким образом, нам потребуется найти компромисс между точностью решения задачи и временными затратами. Теперь займемся технической стороной вопроса. Что следует передавать нашей функции в качестве параметров? Я вспомнил о великолепной встроенной функции eval(), которая может вычислять выражения JavaScript, переданные ей в виде строки. Поэтому функция integral() для вычисления интеграла будет принимать строку, содержащую выражение вида f(x), в котором переменную обозначим строчной латинской буквой x, например, "5*x*x+10". Два других параметра — числа a и b, соответствующие концам интервала интегрирования.
Определение функции integral() приведено ниже.
function integral(expression, a, b){ // интеграл
 if (!expression || !b&&!a) return 0;
 if (a == b) return 0;
 var x, y1, y2, n, length, dx, S=0;
 length = Math.abs(b - a);
 y1 = Math.min(a,b); 
 b = Math.max(a,b);  
 a = y1;
 n = 100;
 if (length>2) n=Math.round(100*Math.log(length+1));
 dx = length/n;   // длина элементарного отрезка
 x = a;       // начальное значение переменной x в выражении
 y1 = eval(expression);
 x = a + dx;
 y2 = eval(expression);
 S = (y1 + y2)*dx/2;
 for(i=2; i<=n; i++){
  y1 = y2;
  x = x + dx;
  y2 = eval(expression);
  S+= (y1 + y2)*dx/2;
 }
return S
}
Выполним проверку:
integral("x*x", 0, 10)  /* 333.33749999999906
           (точное значение=333.3333...) */

Вычисление производной

Производная выражения (функции) f(x) с одной переменной x в некоторой точке a интерпретируется как скорость изменения значения этого выражения при значении x, равном a. Например, если выражение f(x) описывает зависимость пройденного пути от времени, то производная от f(x) в момент времени t равна скорости движения в этот момент. Алгоритм решения этой задачи таков: необходимо вычислить значения выражения в двух тестовых точках, расположенных рядом с заданной, а затем взять разность этих величин и разделить ее на расстояние между тестовыми точками. Чем ближе расположены тестовые точки к заданной, тем точнее получается результат. Мы выберем их так, чтобы заданная точка находилась посередине между тестовыми. Как и интегрирование, вычисление производной будет базироваться на встроенной функции eval(), возвращающей значение выражения, переданное ей в виде строки как параметр. Функция Dydxl() для вычисления производной принимает строку, содержащую выражение, в котором переменная обозначена строчной латинской буквой x, например, "25*x-2". Второй параметр указывает точку, в которой следует вычислить значение производной.
function Dydx(expression,a){  // производная
 if (!expression) return 0;  // если нет выражения
 var x, y, dx;
 dx = 0.000025;
 x = a — dx;           // 1-я тестовая точка
 y = eval(expression);
 x = a + dx;           // 2-я тестовая точка
 return (eval(expression) — y)/2/dx)
}
Проверочные примеры:
Dydx("x",1);       // 0.9999999999998899 (точное значение равно 1)
Dydx("x*x",1);     // 1.9999999999997797 (точное значение равно 2)

Поиск экстремума

Экстремум выражения (функции) f(x) — это пара чисел (x0, y0), таких, что при подстановке x0 в выражение f(x) вместо переменной x значение этого выражения будет равно своему максимальному или минимальному значению y0. Число экстремумов может быть различным (они могут вообще отсутствовать). Мы напишем код функции extremum(), которая вычисляет экстремум (при его наличии) выражения (график этого выражения имеет "холм" или "впадину"). Алгоритм решения задачи основан на том, что производная выражения в точке экстремума или перегиба равна нулю. Как и в предыдущих примерах, при поиске экстремума будет пользоваться встроенной функцией eval(), возвращающей значение выражения, переданного ей в виде строки как параметр. Функция extremum() для поиска экстремума принимает строку, содержащую выражение, в котором переменная обозначена строчной латинской буквой x, например, "5*x*x-2*x". Второй и третий параметры указывают границы интервала, в котором следует искать x0. Последний, четвертый параметр задает точность поиска.
function extremum(expression, a, b, d){  // экстремум
 if (!d) d=0.001;
 var x = a, y1, y2, i;
 y1 = Dydx(expression, a);
 i = 1;
 while ((x <= b)&&(i <= 1000)) {
  y2 = Dydx(expression, x+d);
  if ((y1<0)&&(y2>=0)||(y2<0)&&(y1>=0)) {
   x = x + d*Math.abs(y1)/(Math.abs(y1)+Math.abs(y2));
   return new Array(x, eval(expression))
  }
  x+= d;
  y1 = y2;
  i++
 }
 return new Array()
}
Проверочный пример:
extremum("x*x-1",-1, 1, 0.01)  /* (7.528699885739343e-16, -1)
                  точное значение равно (0, -1) */
Обратите внимание, что при наличии нескольких "холмов" или "впадин", функция определит лишь один из них, ближайший к точке a.