Вопрос

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Ответить
AlexPavel
новенький
Сообщения: 12
Зарегистрирован: 28.12.2009 19:31:43

Вопрос

Сообщение AlexPavel »

Не выполняется условие:

Код: Выделить всё

program Project1;
{$mode objfpc}{$H+}
var Y: Real;
i:Integer;
begin
Y:=0;
while Y<1 do
begin
Y:=Y+0.1;
Writeln('Y=',Y:1:2);
end;
Readln;
end.


То есть цикл не прекращается при Y=1, а прекращается при Y=1.01. При выполнении выше указанного условия, но с Y<2 или Y<3, цикл прекращается при Y=2 или Y=3 соответственно.
Подобная проблема и со следующей конструкцией:

Код: Выделить всё

program Project1;
{$mode objfpc}{$H+}
var Y: Real;
i:Integer;
begin
Y:=0;
for i:=1 to 20 do
begin
Y:=Y+0.1;
if Y=1.0 then Y:=0;
Writeln('Y=',Y:1:2);
end;
Readln;
end.


То есть не выполняется условие в цикле (при Y=1 переменная Y должна принять значение Y=0). Необходимо выполнение условие равенства.
Как можно решить данную проблему?
Putnick
новенький
Сообщения: 62
Зарегистрирован: 18.03.2009 12:02:56

Сообщение Putnick »

Уважаемый AlexPavel.
По всей видимости, проблема возникает в результате ошибки(?) представления вещественных чисел. Дело в том, что это для нас 0.1 — это 1/10, а для машины это, скажем, 6/64, или 102/1024. Причем, все вычисления проводятся именно с такими дробями, и только при выводе их на экран они округляются до десятичных дробей. Короче — "не верь глазам своим" :wink: .
Наиболее простое решение выглядело бы следующим образом:
1. Задаёмся некоей предельно-допустимой ошибкой
2. В условии проверяем abs(MaxY-Y)>Err.

Код: Выделить всё

program Project1;
const
  Err=0.0000000000001;
  MaxY=1;
var
  Y: Real;
  i:Integer;
begin
  Y:=0;
while abs(MaxY-Y)>Err do begin
  Y:=Y+0.1;
  Writeln('Y=',Y);
end;
Readln;
end.

Кстати, например, если Вы прогоните предложенную программу с MaxY=25, то последнее, что она выдаст, будет — 25,0000000000001. Тоже перебор, однако :) .
Надеюсь, смог помочь.
С уважением, Алексей.
Vadim
долгожитель
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Сообщение Vadim »

AlexPavel
Замените тип Real на тип Single и будет Вам счастье. :)
Ещё лучше, если Вы в своих программах вообще забудете про тип Real, т.к. он нужен был только тогда, когда математические сопроцессоры были дороги и ими компы не комплектовались для беднейших слоёв населения, вроде нас с Вами. :)
Тип Real - это эмуляция числа с плавающей точкой, поэтому там и возникают подобные финтифлюшки. Математический сопроцессор, который работает с реальной плавающей точкой (типы Single, Double, Extended) работает более точно. ;)
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

Счастье не наступит.
Тип real соответствует double уже лет десять как минимум.
А тот эмулируемый real - в Дельфи называется real48, а в FPC его скорее всего вообще нет.
Vadim
долгожитель
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Сообщение Vadim »

Sergei I. Gorelkin писал(а):Счастье не наступит.

Позвольте с Вами не согласится, учитель. :) Счастье наступит. :D
"Практика - единственный критерий истины" (Основы марксизма-ленинизма :D )
Прежде чем писать, я проверил на практике. С типом Single всё тип-топ. ;)

Добавлено спустя 5 минут 50 секунд:
Sergei I. Gorelkin писал(а):Тип real соответствует double уже лет десять как минимум.

А вот на счёт Double я с Вами согласен. Что-то я об этом не подумал. :(
Сейчас проверил, подсталил вместо Real Double - вывод на экран идёт такой же, как и с Real.
Odyssey
энтузиаст
Сообщения: 580
Зарегистрирован: 29.11.2007 16:32:24

Сообщение Odyssey »

AlexPavel писал(а):

Код: Выделить всё

if Y=1.0 then Y:=0;

Использовать операции точного сравнения (= и <>) для типов с плавающей точкой -- это нарываться на неприятности. Достаточно какой-нибудь единички в десятом разряде после запятой, и числа уже не равны. Для решения можно например:
1) задаться необходимой точностью, домножить число на 10 в соответствующей степени, округлить (или отбросить дробную часть) и сравнивать уже полученные целые числа:

Код: Выделить всё

if Trunc(Y)=1 then Y:=0;

Код: Выделить всё

if Round(Y*10)=10 then Y:=0;

2) задаться допустимой погрешностью и сравнивать абсолютную разность между числами с этой погрешностью:

Код: Выделить всё

if Abs(Y-1) < 0.1 then Y:=0;


Вот тут есть неплохая статья на эту тему:
http://xpoint.ru/know-how/Articles/FloatingPointNumbers
Аватара пользователя
Astralis
новенький
Сообщения: 45
Зарегистрирован: 06.06.2007 20:33:05
Откуда: Tvercity-Annet
Контактная информация:

Сообщение Astralis »

Есть еще возможность отказаться от типов с плавающей точкой в пользу Integer и Currency (тип с фиксированной точкой). В случае использования типа Integer как правило берут счетчик i и имеют в итоге a+h*i, где h - размер шага, a - начальное значение и все сходится. Использование типа Currency имеет всегда подводные камни, поскольку во многих операциях компилятор стремится все равно преобразовать Currency в Extended. Есть еще вариант поискать где-нибудь в интернете реализацию класс Rational (рациональные числа).
Наконец, если нет очевидных вариантов решения, то попробуйте произвести более глубокий анализ задачи. На памяти был случай, когда студенту давали задание определить тип треугольника (остроугольный, прямоугольный, тупоугольный). Можно сразу определить косинус наибольшего угла и сравнить его с нулем, однако в этом случае есть риск попасть именно в ловушку типов с плавающей точкой. А если попытаться проверить прямоугольность треугольника по теореме Пифагора - то никаких проблем не будет=)
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

И еще: в приведенном коде значение с плавающей точкой увеличивается в цикле, при этом погрешность вычислений накапливается.
Если же вычислять его с помощью умножения (Y := X * 0.1, где X - целое), то накопления происходить не будет. Заодно и цикл получается по целым числам.
Ответить