FORUM
От имени группы читателей к нам обратился С.Е. Троицкий из Москвы с просьбой прокомментировать явление, с которым они столкнулись в своей работе и дать рекомендации по путям его обхода.
Проблема связана с организацией работы в циклах, когда параметр цикла задан числом с плавающей точкой. Суть проблемы хорошо демонстрируется на двух следующих примерах.
10 FOR i=0 TO 1 STEP 0.01 20 IF i <> 0.5 THEN PRINT i 30 NEXT i
По этой программе компьютер печатает все значения от 0 до 1, в том числе и 0.5 (?!).
10 FOR i=0 TO 1 STEP 0.01 20 IF i=0.5 THEN PRINT 1 30 NEXT i
Здесь компьютер ничего не печатает (!?).
Примеры можно продолжать, но суть ясна - что-то не так и надо что-то делать.
Нас в этом вопросе заинтересовала прежде всего его академичность. Проблема в общем-то типовая и, что самое интересное - она имеет отношение не только к "Спектруму", а вообще к любым видам вычислительной техники и знать пути обхода ее надо каждому, кто собирается применять компьютер в инженерных вычислениях.
Итак, начнем. Вы знаете, что компьютер хранит числа в байтах, каждый из которых состоит из восьми битов. Для целого числа от 0 до 65535 достаточно и двух байтов, но для действительных чисел с плавающей точкой этого недостаточно - здесь "Спектрум" использует 5 байтов на каждое число (другие компьютеры могут использовать другое количество байтов, но суть проблемы не меняется). Запись действительного числа в 5-ти байтной форме называется интегральным представлением числа. Хотите узнать все подробно - читайте наш трехтомник по программированию в машинных кодах - там это доступно изложено.
Хоть 5 байтов это и немало - целых 40 битов, тем не менее точность, с которой число может быть занесено в память, все равно не бесконечная. Так, для "Спектрума" эта точность - 8 значащих цифр.
После всего вышесказанного Вы не будете удивляться, если число 0.01 будет храниться в памяти компьютера как 0.0099999999.
Теперь дело за процедурами. Процедуры, выполняющие сложение, после 50 проходов по Вашему циклу могут привести к тому, что вместо 0.5 параметр цикла i может иметь что угодно от 0.049999949 до 0.049999999, но никак не 0.5. И процедура сравнения в строке 20 однозначно ответит, что эти числа "НЕ РАВНЫ". Тогда в действие вступит процедура печати, которая в первом примере должна напечатать 1. Она округлит имеющийся результат и выдаст 0.5.
Другое дело имеет ли она право выполнять такое округление. На вычислительных системах, предназначенных для выполнения инженерных и физических расчетов конечно никто такого права компьютеру не даст. "Спектрум" же - бытовая машина и домохозяйке (школьнику, студенту, поэту, журналисту, рабочему и др.) гораздо приятнее видеть на экране 0.5, чем громоздкое 0.049999949 или еще того лучше 4.9999949 E-02.
Теперь другое дело - как Вам это обойти, чтобы сравнение все-таки работало и работало правильно.
Во-первых, давайте договоримся, что такие операции нужны прежде всего в инженерных расчетах, а они, как известно, выполняются всегда с ограниченной точностью. Например, вычисляя длину окружности по ее радиусу и закладывая радиус равным, например 0.25 м Вы тем самым утверждаете, что радиус вам известен с точностью равной двум значащим цифрам. Какой же Вам смысл получать длину окружности с точностью до 8 значащих цифр, если Вы знаете, что все равно цифрам от третьей до восьмой веры нет?
Во вторых, давайте введем на время Ваших вычислений понятие точности.
Предположим для приведенных выше примеров Вас устраивает точность порядка 0.00001. И теперь давайте с ней работать. Обозначим эту нормативную (необходимую Вам) точность переменной eps (обычно в инженерных расчетах погрешность обозначают греческой буквой "эпсилон").
Теперь Ваша программа будет иметь следующий вид:
5 LET eps = 0.00001 10 FOR i = 0 то 1 STEP 0.01 20 IF ABS(i-0.5) > eps THEN PRINT i 30 NEXT i или
5 LET eps = 0.00001
10 FOR i = 0 TO 1 STEP 0.01
20 IF ABS(i-0.5) < eps THEN PRINT i
30 NEXT i
Итак, рецепт прост: "Если ваши числа представляют числа с плавающей точкой, то вместо того, чтобы сравнивать их одно с другим, надо вычислять разность сравниваемых чисел и сравнивать абсолютную величину это разности с разумно заданной Вами величиной допустимой погрешности eps."
Подобными приемами надо пользоваться всегда, когда Вы ведете инженерные расчеты и выделяете какое-то сравнение. Это не зависит от того "Спектрум" это или "Эльбрус" или "Коммодор". Это стиль инженерных вычислений и его надо выдерживать, чтобы быть гарантированно защищенным от возможных ошибок, связанных с формами представления чисел в известной или неизвестной Вам машине.
Ну и наконец последнее замечание. Использовать в качестве параметра цикла число с плавающей точкой - это "дурной тон". На большинстве компьютеров это приводит как минимум к замедлению скорости вычислений и делать это просто не принято. Параметр цикла надо делать целым числом. И тогда наши примеры будут выглядеть так: 5 LET eps = 0.00001 10 FOR i=0 TO 100 15 LET а=1/100
20 IF ABS(a-0.5) > eps THEN PRINT a 30 NEXT i или
5 LET eps - 0.00001 10 FOR i=0 TO 100 15 LET a=1/100
20 IF ABS(a-0.5) < eps THEN PRINT a 30 NEXT i