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

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

Все способы изучить Python Все способы изучить Python

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

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

Думаю, что всем, даже начинающим, знаком условный оператор (или оператор 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.

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

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


Директивы компилятора Директивы компилятора
Как это ни странно, но даже многие опытные программисты не используют директивы компилятора, считая их чем-то ненужным и бесполезным. А между тем, директивы компилятора - это очень классная штука. Если их умело применять в своих программах, то можно существенно сократить время на разработку и уменьшить количество рутинных операций. Подробнее...
Инфо-МАСТЕР ®
Все права защищены ©
e-mail: mail@info-master.su

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