Отправляет email-рассылки с помощью сервиса Sendsay

Всё о работе в Интернет

  Все выпуски  

Занятие 30.


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

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

РЕШЕНИЕ ЛИНЕЙНОГО УРАВНЕНИЯ

1. Решение задачи “Линейное уравнение”.

Задача V.2.6. “Линейное уравнение”. Заданы коэффициенты a и b линейного уравнения a · x + b = 0. Построить алгоритм и подпрограмму решения уравнения.

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

Математические предпосылки решения линейного уравнения таковы:

-       если коэффициент a <> 0, то уравнение имеет единственный корень и его легко вычислить по формуле x = –b / a; например, уравнение 2 · x – 3 =0 имеет корень x = 3 / 2 = 1.5; в противном случае приходится дополнительно анализировать значение коэффициента b;

-       коэффициент a = 0; если при этом и коэффициент b = 0, то любое значение x является корнем уравнения, поскольку равенство 0 · x = 0 справедливо при любом x; в этом случае говорят, что уравнение имеет бесчисленное множество корней;

-       коэффициент a = 0; если при этом коэффициент b <> 0, то уравнение не имеет корней, поскольку равенство 0 · x = b невозможно ни при каком x.

Таким образом, алгоритм решения линейного уравнения должна иметь два результата. Основной результат – корень x, который может быть вычислен не при любых значениях коэффициентов уравнения. Второй результат – флажок z, который для всех случаев должен идентифицировать характер результата решения. Это достигается присвоением ему значений соответствующих кодов (1 – корень единственный, 2 – корней бесчисленное множество, 3 –  корни отсутствуют). Тем самым при использовании алгоритма в качестве вспомогательного создаётся возможность определять, получено или не получено конкретное значение корня.

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

В первую очередь следует проанализировать условие a <> 0. При этом всё множество значений аргументов a и b, для которых a <> 0, соответствует наличию единственного корня и дальнейшему разбиению не подлежит, а условие это может затем уже не учитываться.

Оставшиеся две ветви команды выбора автоматически соответствуют условию a = 0, и их построение может быть связано с анализом либо условия b = 0, либо условия b <> 0. В алгоритме рассматривается условие b = 0, которое записывается чуть-чуть короче.

Было бы ошибкой начать построение ветвей команды выбора с анализа значения коэффициента b. Дело в том, что из двух образовавшихся множеств значений аргументов, соответствующих условиям b = 0 и b <> 0, каждое снова пришлось бы разбивать на подмножества в зависимости от условия a <> 0.  

Алгоритм решения задачи и его перевод на Паскаль выглядят следующим образом:

алг ЛУр ( арг a, b: вещ; рез x: вещ, z: нат )
нач
 
  выбор
      при a <> 0: нс x := – b/a; z := 1 кс
      при
b = 0: z := 2
      иначе
z := 3
   кв
кон

Procedure Lineal_Equation ( a,b: Real; Var x: Real; Var z: Byte);
Begin
   If a<>0 Then Begin x:= – b/a; z:=1 End
      Else If b=0 Then z:=2 Else z:=3
End;

2. Решение задачи “Совпадение корней”.

Задача V.3.3. “Совпадение корней”. Построить подпрограмму для определения, имеет ли линейное уравнение a ∙ x – b = 0 корень, удовлетворяющий линейному уравнению c ∙ x – d = 0. Если такой корень имеется, то нужно вывести его значение. Необходимо также охарактеризовать все варианты взаимного соответствия корней этих уравнений.

В данной задаче требуется выполнить совместный анализ решений двух заданных линейных уравнений. Характер результата решения каждого линейного уравнения идентифицируем флажками z1 и z2 соответственно. Поскольку характер результата может быть одним из трёх возможных (см. предыдущую задачу), то, таким образом, возможно всего девять различных их сочетаний. Результаты совместного анализа сочетаний представлены в таблице. Кроме того, в этой же таблице указаны значения результирующего флажка z, отображающего сходные ситуации.

z1

z2

Результат совместного анализа

z

1

1

Каждое из уравнений имеет корень. Корни равны. Можно вывести значение любого из них. Ответ на вопрос задачи положителен.

1

Каждое из уравнений имеет корень. Корни различны. Ответ на вопрос задачи отрицателен.

2

1

2

Второе уравнение имеет бесчисленное множество корней. Следовательно, ему удовлетворяет и корень первого уравнения. Ответ на вопрос задачи положителен. Следует вывести корень первого уравнения.

1

1

3

Второе уравнение не может иметь корней. Ответ на вопрос задачи отрицателен.

3

2

1

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

1

2

2

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

4

2

3

Второе уравнение не может иметь корней. Ответ на вопрос задачи отрицателен.

3

3

1

Первое уравнение не может иметь корней. Ответ на вопрос задачи отрицателен.

5

3

2

Первое уравнение не может иметь корней. Ответ на вопрос задачи отрицателен.

5

3

3

Первое уравнение не может иметь корней. Ответ на вопрос задачи отрицателен.

5

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

Из значений флажков,  полученных в результате решения каждого уравнения в отдельности (первые два столбца таблицы), можно образовать некоторое объединённое значение – комбинированный флажок. Для этого каждую пару значений флажков нужно представить в виде целого двузначного числа (умножить значение первого флажка z1 на 10 и добавить к результату значение второго флажка z2. Такой целочисленный комбинированный флажок удобно использовать в операторе Case для организации множественного ветвления.

В первой ветви оператора Case проанализируем ситуацию наличия единственных корней у обоих уравнений (значение комбинированного флажка z1*10 + z2 = 11). Для этого  вычислим разность корней обоих уравнений x1  x2, имея в виду, что она должна быть равна нулю в случае равенства корней, и отлична от нуля – в противном случае.

Однако, из материалов предыдущих занятий нам уже известно, как “подозрительно” может вести себя разность вещественных чисел. Поэтому, для правильного сравнения корней двух линейных уравнений на ряде примеров предварительно проконтролируем значение разности корней x1  x2. С этой целью можно даже составить маленькую простенькую программку:

   Const a: Real = 0.99; b: Real = -0.33;
         c: Real = 1.41; d: Real = -0.47;
   Var x1, x2: Real;
Begin
   x1 := -b/a; x2 := -d/c;
   WriteLn('x1=',x1,' x2=',x2,' x1-x2=',x1-x2)
End.

Наши опасения оказались не напрасными. Для линейных уравнений 0.99 ∙ x – 0.33 = 0 и 1.41 ∙ x – 0.47 = 0, корни которых одинаковы и равны x1 = x2 = 1 / 3, получена неприятность в виде неравных корней x1 = 3.33333333333485E–0001 и x2 = 3.33333333333030E–0001, а также ненулевого значения разности x1–x2 = 4.54747350886464E–0013.

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

Попробуем повысить точность вычислений. Для этого тип Real заменим типом Extended:

   Type Real = Extended;
   Const a: Real = 0.99; b: Real = -0.33;
         c: Real = 1.41; d: Real = -0.47;
   Var x1, x2: Real;
Begin
   x1 := -b/a; x2 := -d/c;
   WriteLn('x1=',x1,' x2=',x2,' x1-x2=',x1-x2)
End.

Ура! Результат стал правильным: x1 = 3.33333333333333E–0001, x2 = 3.33333333333333E–0001, x1–x2 = 0.

Казалось бы, цель достигнута. К сожалению, успех этот временный. При использовании типа Extended решим теперь линейные уравнения 7 ∙ x – 1 = 0 и 7.7 ∙ x – 1.1 = 0, корни которых одинаковы и равны x1 = x2 = 1 / 7:

   Type Real = Extended;
   Const a: Real = 7; b: Real = -1;
         c: Real = 7.7; d: Real = -1.1;
   Var x1, x2: Real;
Begin
   x1 := -b/a; x2 := -d/c;
   WriteLn('x1=',x1,' x2=',x2,' x1-x2=',x1-x2)
End.

Получаем корни x1 = 1.42857142857143E–0001 и x2 = 1.42857142857143E–0001, а их разность x1–x2 = –1.35525271560688E–0020.

Внешне корни одинаковы. Однако результат вычитания показывает, что они разные, хотя и отличаются очень мало. Очевидно, в данном случае не совпадают двоичные машинные представления корней. Следовательно, и здесь мы получим ошибочный результат совместного анализа решений этих уравнений.

Как показывает значение разности в обоих примерах, различие корней весьма незначительно и возникает в последних цифрах их значений. Эти последние цифры представляют собой “мусор”, от которого следует избавиться, чтобы обеспечить правильный результат (см. материалы занятия 14).

Для данной задачи договоримся считать “мусором” всё то, что находится далее пятого разряда после десятичной точки. Соответственно будут считаться нулевыми все значения, абсолютная величина которых меньше 10-5. Вычисленную разность r := x1  x2 очистим от “мусора” с помощью оператора r := Int( r * 1E5 ) / 1E5. Если корни равны, то этим самым для значения r будет получен “чистый” нуль. Только после этого анализ значения разности r позволит сделать правильный вывод о равенстве или неравенстве значений корней.

Решение рассмотренной задачи оформлено в виде процедуры Coincidence. Соответствующая программа имеет вид:

{$B+,D+,E+,I+,L+,N+,Q+,R+,X-}
Program V_03_03;
   Procedure Lineal_Equation ( a,b: Real; Var x: Real; Var z: Byte);
   Begin
      If a<>0 Then Begin x:=
b/a; z:=1 End
         Else If b=0 Then z:=2 Else z:=3
   End;
   Procedure Coincidence (a,b,c,d: Real; Var x: Real; Var z: Byte);
      Var x1,x2,r: Real; z1,z2: Byte;
   Begin
      Lineal_Equation(a,b,x1,z1);
      Lineal_Equation(c,d,x2,z2);
      z:=z1*10+z2;
      Case z Of
         11: Begin r:=x1-x2; r:=Int(r*1E5)/1E5;
                   If r=0 Then Begin x:=x1; z:=1 End Else z:=2 End;
         12: Begin x:=x1; z:=1 End;
         13,23: z:=3;
         21: Begin x:=x2; z:=1 End;
         22: z:=4;
         31..33: z:=5;
      End;
   End;
   Var a,b,c,d,x: Real; z: Byte;
Begin
   Write('Введите коэффициенты a,b уравнения 1: ');

   ReadLn(a,b);
   Write('Введите коэффициенты c,d уравнения 2: ');
   ReadLn(c,d);
   Coincidence(a,b,c,d,x,z);
   Case z Of
      1: WriteLn('Да.
Корень x=',x
:0:4);
      2:
WriteLn
('Нет. Корни уравнений различны');
      3:
WriteLn
('Нет. Уравнение 2 не может иметь корней');
      4:
WriteLn
('Да. Корень произвольный');
      5:
WriteLn
('Нет. Уравнение 1 не может иметь корней');
  
End

End.

3. Совместимость типов.

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

Совместимость типов следует понимать исключительно в синтаксическом смысле. Это значит, что компилятор выносит вердикт о совместимости типов величин, не принимая во внимание конкретные значения этих величин.

Компилятором различаются следующие ситуации совместимости типов:

-      совместимость типов операндов в выражении;

-      совместимость типов переменной и выражения в операторе присваивания;

-      совестимость типов фактических и формальных параметров при обращении к процедуре или функции.

Операнды в выражениях (а также в отношениях) считаются совместимыми по типу, если:

-      все операнды имеют один и тот же тип;

-      типы одних операндов являются отрезками типов других;

-      типы операндов являются отрезками одного и того же базового типа;

-      все операнды имеют любой числовой тип, в том числе и интервальный.

В выражениях с разнотипными операндами числового типа автоматически происходит преобразование менее мощных типов в более мощные, а также преобразование целочисленных типов в вещественные. Всё это происходит по мере необходимости в порядке вычисления выражения.

Например, при Const m: ShortInt=100; n: Integer=32000; k: LongInt=2; выполнение оператора присваивания k:=m*n*k невозможно из-за переполнения. Однако ситуацию легко исправить, представив тот же оператор присваивания в виде k:=k*m*n или в виде k:=LongInt(m)*n*k. Первый из предложенных вариантов исправления демонстрирует автоматическое преобразование типа операнда в выражении к более мощному, второй –  принудительное. Во втором варианте идентификатор порядкового типа LongInt использован как имя функции преобразования типа.

Уважаемые подписчики! На следующем занятии мы с вами продолжим решение уравнений. Кроме того, мы подробнее поговорим о функциях преобразования типов.

Уважаемые подписчики! При необходимости задать вопрос, проконсультироваться, уточнить или обсудить что-либо обращайтесь через Гостевую книгу моего персонального сайта http://a-morgun.narod.ru. При этом настоятельно рекомендую пользоваться браузером Internet Explorer.

С уважением, Александр.

 


В избранное