Вопрос

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

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

Вопрос

Сообщение AlexPavel » 01.01.2010 21:31:07

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

Код: Выделить всё
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). Необходимо выполнение условие равенства.
Как можно решить данную проблему?
AlexPavel
новенький
 
Сообщения: 12
Зарегистрирован: 28.12.2009 20:31:43

Re: Вопрос

Сообщение Putnick » 02.01.2010 00:39:24

Уважаемый 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. Тоже перебор, однако :) .
Надеюсь, смог помочь.
С уважением, Алексей.
Putnick
новенький
 
Сообщения: 62
Зарегистрирован: 18.03.2009 13:02:56

Re: Вопрос

Сообщение Vadim » 02.01.2010 07:05:28

AlexPavel
Замените тип Real на тип Single и будет Вам счастье. :)
Ещё лучше, если Вы в своих программах вообще забудете про тип Real, т.к. он нужен был только тогда, когда математические сопроцессоры были дороги и ими компы не комплектовались для беднейших слоёв населения, вроде нас с Вами. :)
Тип Real - это эмуляция числа с плавающей точкой, поэтому там и возникают подобные финтифлюшки. Математический сопроцессор, который работает с реальной плавающей точкой (типы Single, Double, Extended) работает более точно. ;)
Vadim
долгожитель
 
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Re: Вопрос

Сообщение Sergei I. Gorelkin » 02.01.2010 07:50:44

Счастье не наступит.
Тип real соответствует double уже лет десять как минимум.
А тот эмулируемый real - в Дельфи называется real48, а в FPC его скорее всего вообще нет.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Вопрос

Сообщение Vadim » 02.01.2010 09:35:41

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

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

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

А вот на счёт Double я с Вами согласен. Что-то я об этом не подумал. :(
Сейчас проверил, подсталил вместо Real Double - вывод на экран идёт такой же, как и с Real.
Vadim
долгожитель
 
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Re: Вопрос

Сообщение Odyssey » 02.01.2010 13:11:41

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
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Вопрос

Сообщение Astralis » 03.01.2010 19:32:03

Есть еще возможность отказаться от типов с плавающей точкой в пользу Integer и Currency (тип с фиксированной точкой). В случае использования типа Integer как правило берут счетчик i и имеют в итоге a+h*i, где h - размер шага, a - начальное значение и все сходится. Использование типа Currency имеет всегда подводные камни, поскольку во многих операциях компилятор стремится все равно преобразовать Currency в Extended. Есть еще вариант поискать где-нибудь в интернете реализацию класс Rational (рациональные числа).
Наконец, если нет очевидных вариантов решения, то попробуйте произвести более глубокий анализ задачи. На памяти был случай, когда студенту давали задание определить тип треугольника (остроугольный, прямоугольный, тупоугольный). Можно сразу определить косинус наибольшего угла и сравнить его с нулем, однако в этом случае есть риск попасть именно в ловушку типов с плавающей точкой. А если попытаться проверить прямоугольность треугольника по теореме Пифагора - то никаких проблем не будет=)
Аватара пользователя
Astralis
новенький
 
Сообщения: 45
Зарегистрирован: 06.06.2007 20:33:05
Откуда: Tvercity-Annet

Re: Вопрос

Сообщение Sergei I. Gorelkin » 03.01.2010 20:28:52

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


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru