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

Программирование с нуля для инженера - выпуск 8


Информационный Канал Subscribe.Ru

Программирование с нуля для инженера - логические операции и выражения, условия (выпуск 8)
  • Архив рассылки: ssdg.h15.ru/resources.php?internalpart=maillist
  • Автор рассылки: Седлярский Илья
  • Периодичность выхода: раз в две недели
  • Содержание
    Вступление
    В шестом выпуске я писал о погрешностях при расчётах с дробными числами, но забыл упомянуть об одном важном моменте. Посмотрите на участок кода из программы на языке QuickBasic:
    FOR i = 1 TO n
      ex = ex + x(i)
      ey = ey + y(i)
      exy = exy + x(i) * y(i)
      ex2 = ex2 + x(i) ^ 2
      ey2 = ey2 + y(i) ^ 2
    NEXT i
    
    IF n * ex2 - ex ^ 2 = 0 THEN
      PRINT
      COLOR 12: PRINT "Entered values are not valid. Division by zero."
      PRINT "Reenter values.": COLOR 7
      PRINT
      GOTO 10
    END IF
    Обратите внимание на условие IF n * ex2 - ex ^ 2 = 0 THEN. Выражение "n * ex2 - ex ^ 2" в редком случае будет абсолютно точно равно нулю. Оно может быть сильно близко к нему. Если результат расчёта используется в дальнейших расчётах, погрешность не так существенна, как в этом случае. Подставляя данные, при которых выражение должно быть равно нулю, вы можете не получить ноль, и условие не выполнится. Для разрешения ситуации нужно ввести константу - допускаемое отклонение разности от нуля, при котором эту разность можно считать за ноль.
    Этот же код, переписанный на Delphi, консольная версия:
    Const
      ZERO_DISTANCE = 0.000001;
      
    Begin
      //...
      For I := 1 To N Do
      Begin
        ex  := ex + X[I];
        ey  := ey + Y[I];
        exy := exy + X[I] * Y[I];
        ex2 := ex2 + Sqr(X[I]);
        ey2 := ey2 + Sqr(Y[I]);
      End;
    
      If Abs(N * ex2 - Sqr(ex)) < ZERO_DISTANCE Then
      Begin
        WriteLn('Entered values are not valid. Division by zero.');
        Write('Please run program again and reenter values!');
        ReadLn;
        Exit;
      End;
      //...
    End.
    Функция Sqr(X) возвращает квадрат числа. Функция Abs(X) возвращает модуль числа.
    Если модуль разности N * ex2 - Sqr(ex) меньше константы ZERO_DISTANCE, будем считать, что выражение равно нулю. Значение константы подбирается в зависимости от типов используемых данных (чем точнее типы, тем меньше константа) и от задачи, которую решает программа. Такой подход следует применять и в случае, когда вы сравниваете два значения, полученные в результате расчёта. Для сравнения значений нужно взять их разность и использовать её в вышеописанном условии.
    Эту программу мне прислал подписчик Андрей из Ташкента. Большое вам спасибо, Андрей!
    Логические операции
    Логические операции делятся на два типа: логические и логические поразрядные (или просто поразрядные). Логические операции служат для формирования составных логических выражений из простых.
    Поразрядные операции работают с целыми числовыми и с булевыми операндами. При этом каждый бит операндов обрабатывается независимо от остальных битов. Поэтому операции называются побитовыми или поразрядными. Рассмотрим сначала поразрядные операции. О логических операциях читайте в разделе выпуска "логические выражения".

    Поразрядное Not
    Not - отрицание, или инверсия, выполняющаяся над одним операндом. Каждый бит, над которым выполняется операция Not, меняет своё значение на противоположное. Если бит содержал 1, после отрицания он будет содержать 0, и наоборот. Для булевых типов данных инвертируется только первый бит переменной. (Not True) = False, (Not False) = True.
    Для целых числовых типов операция Not выполняется над каждым битом. Чтобы оценить результат работы операции Not над целым числом, нужно сначала представить число в двоичной форме, затем инвертировать все биты получившегося числа, а потом преобразовать их обратно в десятичную систему. Например, переменная X типа Byte равна 50. В двоичном представлении это 110010. Дополняем число нулевыми битами слева до 8, поскольку переменная типа Byte занимает в памяти 8 бит. Получается 00110010. Теперь инвертируем все биты. Получаем 11001101. В десятичном виде это 205. При сложении X и его отрицания получаем 255 - максимальное значение типа Byte. Это будет справедливо для любого значения X. Если X - беззнаковое целое число, то сумма X и его отрицания будет всегда равна максимальному значению типа, который имеет X. X + (Not X) = MAX_X. Для знаковых чисел (Not X) = -(X + 1);
    Двойное отрицание даёт первоначальное значение: (Not (Not X)) = X.

    Поразрядное And
    And переводится с английского как "И". Это операция выполняется над двумя операндами.
    X1 0 1 1 0 1 0 0 1 = 105
    X2 1 1 0 1 1 1 0 0 = 220
    X1 And X2 0 1 0 0 1 0 0 0 = 72
    Для целых чисел i-ый бит результирующего значения равен единице в том случае, если i-ые биты первого И второго операндов равны единицам. Иначе i-ый бит результата равен нулю. Для булевых переменных результат равен True, если оба операнда равны True, иначе он равен False.
    • False And False = False
    • False And True = False
    • True And False = False
    • True And True = True
    Операция коммутативна: X And Y = Y And X.

    Поразрядное Or
    Or переводится с английского как "ИЛИ". Операция выполняется над двумя операндами. I-ый бит результирующего значения равен единице, когда хотя бы один из i-ых битов двух операндов равен единице.
    X1 0 1 1 0 1 0 0 1 = 105
    X2 1 1 0 1 1 1 0 0 = 220
    X1 Or X2 1 1 1 1 1 1 0 1 = 253
    Для булевых переменных результат равен True, если хотя бы один из операндов равен True, иначе он равен False.
    • False Or False = False
    • False Or True = True
    • True Or False = True
    • True Or True = True
    Операция коммутативна: X Or Y = Y Or X.

    Поразрядное Xor
    Xor - сокращение от eXclusive OR, переводится с английского как "ИСКЛЮЧАЮЩЕЕ ИЛИ". Операция выполняется над двумя операндами. I-ый бит результирующего значения равен единице, когда только один из i-ых битов двух операндов равен единице.
    X1 0 1 1 0 1 0 0 1 = 105
    X2 1 1 0 1 1 1 0 0 = 220
    X1 Xor X2 1 0 1 1 0 1 0 1 = 181
    Для булевых переменных результат равен True, когда один из операндов равен True, другой равен False. Иначе результат равен False.
    • False Xor False = False
    • False Xor True = True
    • True Xor False = True
    • True Xor True = False
    Операция коммутативна: X Xor Y = Y Xor X. Двойное применение операции даёт первоначальное значение - ((X Xor Y) Xor Y) = X. Это свойство операции Xor используется в шифровании. Операция Xor может быть выражена через Not, And, и Or. X Xor Y = (X And (Not Y)) Or ((Not X) And Y);
    Пример использования логических поразрядных операций. Полная версия в архиве - папка Example1.
    Procedure TMainForm.CalculateBtnClick(Sender: TObject);
    Var
      X:   Byte;
      Y:   Byte;
      Res: Byte;
    
    Begin
      // читаем данные из текстовых полей
      X := StrToInt(InputX_ET.Text);
      Y := StrToInt(InputY_ET.Text);
    
      // получаем Not X и выводим его
      Res := Not X;
      NotX_OutputET.Text := IntToStr(Res);
    
      // получаем Not Y и выводим его
      Res := Not Y;
      NotY_OutputET.Text := IntToStr(Res);
    
      // получаем X And Y и выводим его
      Res := X And Y;
      XandY_OutputET.Text := IntToStr(Res);
    
      // получаем X Or Y и выводим его
      Res := X Or Y;
      XorY_OutputET.Text := IntToStr(Res);
    
      // получаем X Xor Y и выводим его
      Res := X Xor Y;
      XxorY_OutputET.Text := IntToStr(Res);
    End;
    На этом примере вы можете заметить одну интересную особенность. X Or Y = (X And Y) + (X Xor Y).

    Сдвиговые операции влево (Shl) и вправо (Shr)
    Сдвиговые операции применяются к целочисленным переменным и значениям. Операции выполняются над двумя операндами. Первый операнд - сдвигаемая переменная или значение, второй - число бит, на которое необходимо сдвинуть первый операнд. Shl расшифровывается как SHift Left, Shr - SHift Right.
    Например, X := X Shl 2 сдвигает X на 2 бита влево. Пусть переменная X была равна 50 до сдвига. 5010 = 001100102. Сдвиг производится очень легко. Все биты просто передвигаются на 2 позиции влево с сохранением порядка. 00110010 Shl 2 = 11001000. 110010002 = 20010. При любом сдвиге со стороны, противоположной стороне, в которую сдвигается операнд, к числу "дописываются" нулевые биты.
    Для беззнаковых чисел сдвиг влево на N бит эквивалентен умножению на 2N, сдвиг вправо на N бит эквивалентен делению на 2N. Для знаковых чисел сдвиги могут не соответствовать этому правилу. Сдвиги нужно производить аккуратно. Если сдвинуть операнд на большое число бит влево, произойдёт переполнение.

    Пример использования сдвигов у нас будет позже, при рассмотрении цветового типа TColor и работы с ним.
    Поразрядные и сдвиговые операции не работают с вещественными числами!
    Логические выражения
    Теперь рассмотрим логические операции и операторы отношений, используемые для построения логических выражений.

    Операторы отношений
    Обозначение оператора Операция Пример использования
    = Равенство (X And Y) = (X + 10)
    <> Неравенство (X + Y) <> 0
    < Меньше X < Y
    <= Меньше или равно (X + 1) <= (Y Xor 8)
    > Больше X > 0
    >= Больше или равно Not X >= (X And Y)
    Операторы отношений возвращают значения булева типа. Логическое выражение невозможно построить без операторов отношения. Смотрите пример. В архиве этот пример под номером 2 - папка Example2.
    Procedure TMainForm.CalculateBtnClick(Sender: TObject);
    Var
      X:   Byte;
      Y:   Byte;
      Res: Boolean;
    
    Begin
      // Очищаем список. Он может быть не пустым
      ResultList.Clear();
    
      X := StrToInt(InputX_ET.Text);
      Y := StrToInt(InputY_ET.Text);
    
      // записываем в Res результат вычисления логического выражения
      // в Res запишется True, если (X And Y) равно (X + 10),
      // иначе запишется False
      Res := (X And Y) = (X + 10);
      If Res = True Then  // Res равна True, выведем сообщение об этом
        ResultList.Items.Add('(X And Y) = (X + 10) - правда.')
      Else  // Res равна False
        ResultList.Items.Add('(X And Y) = (X + 10) - ложь.');
    
      // Далее всё следует по аналогии.
      // По порядку вычисляем все выражения из таблицы
      // и сообщаем пользователю (или разработчику) результаты
      // вычисления выражений.
    
      Res := (X + Y) <> 0;
      If Res = True Then
        ResultList.Items.Add('(X + Y) <> 0 - правда.')
      Else
        ResultList.Items.Add('(X + Y) <> 0 - ложь.');
    
      Res := X < Y;
      If Res = True Then
        ResultList.Items.Add('X < Y - правда.')
      Else
        ResultList.Items.Add('X < Y - ложь.');
    
      Res := (X + 1) <= (Y Xor 8);
      If Res = True Then
        ResultList.Items.Add('(X + 1) <= (Y Xor 8) - правда.')
      Else
        ResultList.Items.Add('(X + 1) <= (Y Xor 8) - ложь.');
    
      Res := X > 0;
      If Res = True Then
        ResultList.Items.Add('X > 0 - правда.')
      Else
        ResultList.Items.Add('X > 0 - ложь.');
    
      Res := Not X >= (X And Y);
      If Res = True Then
        ResultList.Items.Add('Not X >= (X And Y) - правда.')
      Else
        ResultList.Items.Add('Not X >= (X And Y) - ложь.');
    End;
    Компонент TListBox имеет метод Clear(), который очищает список. Метод Items.Add(S: String) добавляет в список строку S. В этом примере в логических выражениях используются только числовые переменные и константы. Возможно также использование булевых переменных и констант.
    Все перечисленные в таблице логические выражения были простыми. То есть, в каждом из них оператор отношения использовался 1 раз. Составное выражение строится из простых выражений, связанных логическими операциями Not, And, Or, Xor.

    Логическое Not
    Логическое Not используется для отрицания, как и поразрядное. Различие в том, что отрицание работает над результатом вычисления выражения, а не над переменной или константой. Пример: Res := Not (X = Y);. В том случае, когда X равно Y, Res станет равна False. Это выражение можно было записать иначе: Res := X <> Y;. Согласитесь, так понятнее. Выражения, записанные без отрицания, как правило, воспринимаются легче.

    Логическое And
    Логическое And работает над двумя выражениями. Обозначим первое простое выражение как Expr1 (Expr - сокращение от Expression, в переводе с английского "выражение"), второе как Expr2, и построим составное выражение из этих двух. Составное выражение будет равно True в том случае, когда оба входящих в него простых выражения равны True. Пример:
    Var
      X:     Byte;
      Y:     Byte;
      Expr1: Boolean;
      Expr2: Boolean;
      Res:   Boolean;
    
    Begin
      X := 19;
      Y := 120;
    
      // Переменной Expr1 присвоим значение выражения
      // (X + Y) >= 100
      Expr1 := (X + Y) >= 100;
    
      // Переменной Expr2 присвоим значение выражения
      // (X + Y) <= 250
      Expr2 := (X + Y) <= 250;
    
      Res := Expr1 And Expr2;
    
      // Можно подставить вместо переменных Expr1 и Expr2
      // выражения, значения которых они содержат
      Res := ((X + Y) >= 100) And ((X + Y) <= 250);
    
      // В выражении может быть больше двух составных
      Res := (X <> 0) And ((X + Y) >= 100) And
    ((X + Y) <= 250);
    End;
    В этом примере Res будет равно True, когда сумма X + Y находится в промежутке [100, 250].
    Когда логическое выражение состоит более чем из двух простых выражений, сначала рассчитывается значение подвыражения, состоящего из первых двух частей выражения, и это значение используется для дальнейшего вычисления выражения. Так происходит, когда у всех операций, которыми связаны простые выражения, равный приоритет (как в примере выше). Если у каких-то операций приоритет выше, выражения, связанные этими операциями, вычисляются в первую очередь.

    Логическое Or
    Логическое Or выполняется над двумя выражениями. Если хотя бы одно из этих выражений равно True, составное выражение будет равно True. Соединим два выражения из предыдущего примера операцией Or. Res := ((X + Y) >= 100) Or ((X + Y) <= 250). Это выражение будет тождественно истинным. Какой бы ни была сумма X + Y, выражение будет равным True. Но машине не очевидно то, что очевидно человеку. Выражение будет вычисляться, компилятор ничего вам не "скажет".

    Логическое Xor
    Логическое Xor также выполняется над двумя логическими выражениями. Если одно из выражений равно True, другое - False, составное выражение будет равно True. Иначе оно будет равно False. Res := ((X + Y) >= 100) Xor ((X + Y) <= 250). Res будет равным True в том случае, когда сумма X + Y находится в объединении интервалов (-бесконечность, 100) V (250, +бесконечность). Конечно, на практике интервалы не могут быть бесконечны. Они ограничиваются диапазонами значений типов данных.

    Составные логические выражения могут быть сколь угодно сложны. Но они раскладываются на простые, вычислить которые несложно. При написании составных выражений нужно учитывать приоритеты операций, которыми связываются простые выражения. Перечислю логические, поразрядные, и операции отношения в порядке убывания приоритета: not, and, shl, shr, or, xor, =, <>, <, >, <=, >=. Например, при выполнении Res := (X < 0) Or (Y < 3) And (Z <> 0) сначала вычислится (Y < 3) And (Z <> 0), а затем (X < 0) Or ((Y < 3) And (Z <> 0)), где вместо (Y < 3) And (Z <> 0) будет использовано значение этого выражения. Чтобы указать компилятору порядок вычисления выражения, нужно заключить в скобки те участки выражения, которые должны вычисляться в первую очередь. Записав Res := ((X < 0) Or (Y < 3)) And (Z <> 0), вы "заставите" компилятор вычислить выражение (X < 0) Or (Y < 3) в первую очередь.
    В заголовках перед описанием операций я писал слово "логическое", а не "логическая", как следовало бы, поскольку слово "логическая" правильно употребляется со словом "операция". Это не ошибка. Когда говорят о конкретной логической операции, разрешается использовать слово в среднем роде.
    Условия
    Без возможности проверки значения логические выражения бессмысленны. Проверка значений выражений происходит с помощью условий. Для записи условий используются два оператора - условный оператор If и оператор выбора Case.

    Условный оператор If
    Оператор If имеет сокращённую и полную форму.
    // сокращённая форма
    If <логическое выражение> = <логическая константа> Then
    Begin
      // вложенный оператор 1
      // вложенный оператор 2
      //..
      // вложенный оператор N
    End;
    
    // полная форма
    If <логическое выражение> = <логическая константа> Then
    Begin
      // вложенный оператор 1
      // вложенный оператор 2
      //..
      // вложенный оператор N
    End
    Else
    Begin
      // вложенный оператор 1
      // вложенный оператор 2
      //..
      // вложенный оператор N
    End;
    В сокращённой форме условия происходит сравнение вычисленного логического выражения с указанной константой. Если они равны, вложенные в условие операторы выполняются. В противном случае выполняется следующий за условным оператором оператор. В сокращённом виде условный оператор имеет одну ветку - Then.
    Полная форма отличается от сокращённой тем, что в случае неравенства логического выражения и константы будет выполняться блок операторов, следующий после ключевого слова Else - ветка Else. Условия в Delphi записываются очень логично. Можно понять участок кода с условием, не зная языка программирования. Дословный перевод сокращённой формы условия с английского языка выглядит так: ЕСЛИ выражение равно значению ТО выполнить блок операторов.
    Полную форму можно перевести так: ЕСЛИ выражение равно значению ТО выполнить первый блок операторов ИНАЧЕ выполнить второй блок операторов.
    Var
      X: Integer;
      Y: Integer;
      
    Begin
      X := 50;
      Y := 51;
      
      // условие1
      If X = Y Then
      Begin
        ShowMessage('X равно Y');
        X := Y - 1;
      End;
    
      // условие2
      If X = Y Then
      Begin
        ShowMessage('X равно Y');
        X := Y - 1;
      End
      Else
        ShowMessage('Значения не совпадают');
        Close();
      End;
      
      // условие3
      If (X = Y) = True Then
      Begin
        ShowMessage('X равно Y');
        X := Y - 1;
      End
      Else
      If (X > Y) = True Then
      Begin
        ShowMessage('X слишком велико');
        X := X - 1;
      End
      Else
        Close();
    
      // условие4  
      If X * Y < 1000 Then
        ShowMessage('X и Y не слишком велики');
    
      // условие5
      If ((X + Y) / 2 = 50.5) = True Then
        ShowMessage('Сумма X + Y равна 101');
    End;
    Заметьте: перед Else точка с запятой не ставится. Посмотрите на условие3. Записи If (X = Y) = True..., If (X > Y) = True... выглядят непонятно. Но это и есть условия, записанные по правилу. Выражение X = Y при вычислении возвращает булево значение True или False. Поэтому в записи условия мы сравниваем это выражение с булевой константой True. При сравнении логического выражения с константой True проверку разрешается опускать. Если выражение сравнивается с False, проверку нельзя опускать. Например, если вы хотите узнать, является ли X больше Y, вы можете записать If X > Y = True Then... или If X > Y Then.... Условие5 можно было записать так: If (X + Y) / 2 = 50.5 Then....
    Ещё раз посмотрите на условие3. По ветке Else выполняется другое условие. В этом нет ничего особенного. Условие является оператором, который может выполняться внутри другого условия или составного оператора. Ветки условия Then и Else могут состоять из одного оператора, тогда операторные скобки Begin...End писать не обязательно.

    Оператор выбора Case я решил перенести на следующий выпуск, потому что иначе этот выпуск будет слишком большой.
    Выполнение задания из выпуска 5б
    За время, прошедшее с выхода предыдущего выпуска, мне пришло ещё несколько писем с решением задания. Хочу отметить, что некоторые подписчики уже выполнили расчёт корней уравнения. В задании пока требовалось спроектировать графический интерфейс программы. Предлагаю интерфейс своего варианта решения, в котором я постарался учесть положительные моменты ваших вариантов. Смотрите пример 3 - папка Example3. Можете критиковать мой вариант. Коротко об основных требованиях, которые я положил в основу интерфейса.
    • Должен быть представлен общий вид уравнения - Ax2 + Bx + C = 0
    • Для ввода всех исходных данных (коэффициентов уравнения) должно использоваться одно текстовое поле.
    • Результат решения уравнения должен быть представлен не только корнями, а также другой полезной информацией.
    Хочу дать вам пару советов. Лучше не использовать какие-либо цвета, кроме системных. Оставляйте стандартные цвета у всех элементов, включая форму, либо меняйте ВСЕ цвета сразу. Почему? На разных компьютерах системные цвета настроены по-разному. Если вы сменили один-два цвета, а остальные оставили как есть, при смене системных цветов часть элементов интерфейса вашей программы сменит цвет, а часть не сменит (это как раз те, которым вы задавали "свои" цвета). Если вы устанавливали цвета у всех элементов интерфейса, смена системных цветов на внешний вид программы не повлияет.
    Используйте осмысленные имена компонентов, переменных, констант. Это поможет вам же самим разобраться с программой, когда потребуется что-либо изменить, доработать.
    Следующее задание для вас - расчёт корней уравнения и вывод результатов на экран. Необходимо предусмотреть обработку любых корректно введённых данных. Присылайте свои решения!
    Для решения задачи могут потребоваться некоторые математические функции:
    • Sqr(X) - возвращает квадрат числа X.
    • Sqrt(X) - возвращает корень числа X.
    • Abs(X) - возвращает модуль числа X.
    Заключение
    Ссылка на примеры. Советую вам скачивать примеры, если вы настроены заниматься серьёзно. В примерах в качестве комментариев может быть изложена полезная информация, отсутствующая в выпуске. Выпуски рассылки и примеры неразделимы.
    В следующем выпуске будут рассмотрены математические функции, генерация случайных чисел, а также оператор Case, перенесённый из этого выпуска.
    Партнёрские рассылки и сайты.
    Рекомендую подписаться.
    Рассылки Subscribe.Ru
    Изобретения, которые потрясут мир!

    ИДЕИ ДОМАШНЕГО БИЗНЕСА И СЕКРЕТЫ НАРОДНЫХ УМЕЛЬЦЕВ - www.tehnoidei.com.ru. На этом сайте, Вы найдёте огромное количество идей, методик, технологий, ноу-хау и изобретений о которых мало кто знает, а те кто знает, предпочитает молчать. Все самые последние технологии, которые можно разработать самостоятельно в домашних условиях, представлены на http://www.tehnoidei.com.ru/.

    Subscribe.Ru
    Поддержка подписчиков
    Другие рассылки этой тематики
    Другие рассылки этого автора
    Подписан адрес:
    Код этой рассылки: comp.soft.others.prog2eng
    Архив рассылки
    Отписаться
    Вспомнить пароль

    В избранное