Раздел: Как стать программистом / Секреты программирования /

Сравнение вещественных чисел

Lazarus IDE: Основы программирования в Windows Lazarus IDE: Основы программирования в Windows

Несмотря на то, что всё потихоньку уходит в сеть, программирование для настольных компьютеров остаётся востребованным. И будет таковым ещё долго. Ну а самая распространённая операционная система для настольных компьютеров – это по-прежнему Windows. Поэтому любой программист, даже если он собирается стать веб-разработчиком, должен знать хотя бы основы создания программ для Windows. Подробнее...

Когда я начинал изучать программирование, то, конечно, как и все новички, постоянно наступал на многочисленные “грабли”, беспорядочно раскиданные создателями языков программирования и прочими спецами от ИТ-индустрии. Об одних таких “граблях” расскажу в этой статье.

Думаю, что всем, даже начинающим, знаком условный оператор (или оператор if, или инструкция if - его по разному называют). Он часто используется для сравнения двух чисел. Например:

if (x = 2) then … //Здесь что-то делаем

И он прекрасно работает при сравнении порядковых типов. Однако всё меняется, когда приходят они - вещественные типы.

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

Пример:

program test;

var x : real;
    y : real;

begin
  x := 10;
  y := x / 3;
  if (x = (y * 3)) then
    Writeln(x:0:4, ' = ', (y * 3):0:4)
  else
    Writeln(x:0:4, ' НЕ РАВНО ', (y * 3):0:4);

  ReadLn;
end.

Попробуйте догадаться, что выведет эта программа. Как вы понимаете, вопрос с подвохом. Потому что выведет она:

Сравнение вещественных чисел

Оказывается, 10 НЕ РАВНО 10 !!! А вы-то и не знали!

Выполняя указанное в примере сравнение, мы наверняка НЕ получим равенство. Потому что вещественные числа, с которыми работает компьютер, не могут содержать бесконечное количество значащих разрядов, как в математике. Поэтому, разделив 10 на 3, мы получим что-то типа 3,3333, а не 3 ⅓, как в математике. И число троек после запятой будет ограничено.

Если же мы умножим 3,3333 на 3, то получим 9,9999, а вовсе не 10. Такой маленькой разницей может пренебречь человек, но не компьютер, для которого 10 и 9,9999 (да хоть сколько там будет девяток после запятой) - это НЕ одинаковые числа. Вот вам и причина неприятности (хотя на экран при этом будет выводиться число 10, а не 9,9999).

Кроме того, вещественные типы поддерживают лишь ограниченное количество знаков после запятой (зависит от типа). Причём нельзя точно сказать, сколько именно цифр после запятой будут значащими. Поскольку компьютер создаёт числа наподобие 3,3333346 из-за особенностей вычислений с плавающей точкой, то, как вы уже поняли, сравнивать вещественные числа надо осторожно, принимая некоторые меры для устранения ошибок округления.

Ошибка округления как раз и появляется при умножении 3,3333 на 3. Потому что в программах мы не можем с помощью вещественных чисел умножить 3 1/3 на 3, как в математике.

В общем, ещё об одних “граблях” я вам рассказал. Ну а теперь о том, как их обойти.

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

if (x > -0.00001) and (x < 0.00001) then
  WriteLn('x = 0');

Здесь если х входит в диапазон (-0,00001…0,00001), то мы считаем, что х равен 0.

Ну а наш пример можно переписать так:

if ((x - (y * 3)) < 0.00001) and (((y * 3) - x) < 0.00001) then
    Writeln(x:0:4, ' = ', (y * 3):0:4)
  else
    Writeln(x:0:4, ' НЕ РАВНО ', (y * 3):0:4);

Теперь результат будет ожидаем. Отнимая (y * 3) от x, мы получаем значение, близкое к нулю. И это значение мы уже проверяем на вхождение в диапазон -0,00001…0,00001.

Хотя код получился довольно громоздким и не очень понятным. Можно его немного упростить:

if Abs(x - y * 3) < 0.00001 then
  Writeln(x:0:4, ' = ', (y * 3):0:4)
else
  Writeln(x:0:4, ' НЕ РАВНО ', (y * 3):0:4);

Здесь мы используем стандартную функцию Abs, которая возвращает абсолютное значение. То есть теперь нам не обязательно знать, какое выражение больше, х или у * 3. Потому как результатом функции Abs будет положительное число. Его то мы сравниваем с 0,00001, таким образом проверяя диапазон от -0,00001 до 0,00001.

Возможно, что-то осталось непонятным. Тогда повнимательнее прочитайте ещё раз. Ну или просто используйте не думая. Так тоже можно )))

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


Первые шаги в программирование Первые шаги в программирование

Очень небольшая книга, которую можно прочитать буквально за 15 минут. Но эти 15 минут дадут вам представление о том, что такое программирование. И вы даже напишите свою первую программу. Для тех, кто интересуется программированием, но ещё не знает, что это такое, и не пробовал создавать программы (или пробовал, но не получилось). Подробнее...
Помощь в технических вопросах Помощь в технических вопросах

Помощь студентам. Курсовые, дипломы, чертежи (КОМПАС), задачи по программированию: Pascal/Delphi/Lazarus; С/С++; Ассемблер; языки программирования ПЛК; JavaScript; VBScript; Fortran; Python и др. Разработка (доработка) ПО ПЛК (предпочтение - ОВЕН, CoDeSys 2 и 3), а также программирование панелей оператора, программируемых реле и других приборов систем автоматизации. Подробнее...

Инфо-МАСТЕР ®
Все права защищены ©
e-mail: mail@info-master.su

Яндекс.Метрика