[Решено] Странный результат

Вопросы программирования на Free Pascal, использования компилятора и утилит.

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

Ответить
NickZane
незнакомец
Сообщения: 2
Зарегистрирован: 13.11.2011 18:29:22

[Решено] Странный результат

Сообщение NickZane »

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

a,b,c:real;
...
a:=0.05;
b:=0.9;
c:=a+b;
writeln(c=0.95)

Оператор writeln выдаёт false. Почему?
Ну после c:=a+b с становится равным 0.95 и writeln должен выдать true. Или я чегото не понимаю?
Аватара пользователя
teapot
незнакомец
Сообщения: 7
Зарегистрирован: 13.11.2011 15:20:23

Сообщение teapot »

Запустите в режиме отладки и посмотрите на знаки после запятой. Почему freepascal так считает вещественные числа, мне самому интересно - я в этом деле новичок.
Mr.Smart
долгожитель
Сообщения: 1796
Зарегистрирован: 29.03.2008 00:01:11
Откуда: из леса!

Сообщение Mr.Smart »

teapot по оной простой причине. Все вещественные константы в FPC (архитектура i386) соответствуют типу Extended (10 байт), а в данном случае Real имеет другое представление и при сравнение роисходит преобразование типов. При вещественном преобразовании частенько происходит не соответствие в дробной части.
Можете сами убедиться:

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

writeln(SizeOf(Real));
writeln(SizeOf(0.95));
writeln(SizeOf(Extended));


Добавлено спустя 1 час 32 минуты 14 секунд:
на правах К.О. добавлю (может никто не понял). Данная вещь будет работать если:

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

var
  a,b,c: Extended;



:wink:

Добавлено спустя 1 минуту 6 секунд:
или

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

writeln(c=Real(0.95))
Аватара пользователя
teapot
незнакомец
Сообщения: 7
Зарегистрирован: 13.11.2011 15:20:23

Сообщение teapot »

Да, хотя я и не автор, приблизительно ясно. Только непонятно, происходит преобразование типов при сравнении, как Вы сказали, или уже при присвоении. И, кстати, почему тогда эти последние два байта не заполняются принудительно нулями? Или это вопрос истории?

Добавлено спустя 1 минуту 20 секунд:
Простите, я не совсем понимаю, как работает компьютер. Для меня это волшебная коробочка, которая позволяет общаться с друзьями вконтакте.
NickZane
незнакомец
Сообщения: 2
Зарегистрирован: 13.11.2011 18:29:22

Сообщение NickZane »

Проблемка шире. Я здесь лишь более простой пример привел. А вообще есть задача в которой для функции в цикле производится перебор аргументов X: [0.05; 0.95]. А шаг приращения h как раз равен 0.9 и, соответственно, должны выдаваться результаты для f(0.05) и f(0.95). Условием выхода из цикла стоит X<=0.95.

x:=0.05;
while x<=0.95 do
begin
...
x:=x+0.9;
end;

цикл должен выполниться два раза (для х=0.05 и для х=0.95), а он зараза :) выполняется один раз!
То что здесь чтото происходит с соотношением вещественных типов я сразу понял, но незнал как это обойти. Кстати, если использовать Real(0.95) цикл всё равно один раз выполняется. Но я решил проблемку так: while single(x)<=single(0.95) do ... и всё заработало как надо. Спасибо большое за ответы! Особенно Mr.Smart"у.
Аватара пользователя
informat
новенький
Сообщения: 62
Зарегистрирован: 27.10.2010 09:44:20
Откуда: http://informat.name
Контактная информация:

Сообщение informat »

NickZane писал(а):

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

a,b,c:real;
...
a:=0.05;
b:=0.9;
c:=a+b;
writeln(c=0.95)

Оператор writeln выдаёт false. Почему?
Ну после c:=a+b с становится равным 0.95 и writeln должен выдать true. Или я чегото не понимаю?


Действительные числа - ПРИБЛИЗИТЕЛЬНЫЕ. И сравнивать их на равенство бесполезно.
2.0*2.0 = 4.0 может быть НЕ верно.
действительные числа A и B нельзя сравнивать на равенство. Сравнивать можно только приблизительно.
Вместо
A=B
нужно писать
abs(A-B)<0.0001 или с какой точностью нужно.
Vadim
долгожитель
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Сообщение Vadim »

NickZane
Есть специальный тип чисел - бухгалтерский (Currency), призванный решать подобные коллизии. Если знаков после запятой у Вас не более четырёх, то можно использовать этот тип. При этом все сравнения между переменными этого типа проходят корректно.

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

Var
  c1, c2: Currency;

Begin
  c1:=0.95;
  c2:=0.95;
  WriteLn(c1=c2);  //Будет TRUE
  c2:=0.951;
  WriteLn(c1=c2);  //Будет FALSE
End.
SAK
постоялец
Сообщения: 158
Зарегистрирован: 17.02.2006 23:45:14
Откуда: Тим
Контактная информация:

Сообщение SAK »

NickZane писал(а):

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

x:=0.05;
while x<=0.95 do
 begin
    ...
    x:=x+0.9;
end;

Почему не написать "while x<1 do" ? Это будет надёжнее чем "while single(x)<=single(0.95) do" потому как при математичестких операциях 0.95 может превратиться в 0.94999999999999... или 0.95000000000001 или что-то подобное.
Аватара пользователя
and
постоялец
Сообщения: 124
Зарегистрирован: 16.09.2009 17:11:01
Откуда: г. Гомель, Беларусь

Сообщение and »

Хех... И снова старые-старые грабли.
Дело не в FPC и даже не в single vs extended. Все числа в "волшебной коробочке" представляются в виде степеней двойки (стандарт IEEE 754). Из чего следует, что не любая конечная десятичная (т.е. сделанная из степеней десятки) дробь представима в виде конечной двоичной. Так, 0.5=2^(-1) - всё OK. А 0.1 точного представления не имеет.
В Нете много инфы по этому вопросу. Например, вот здесь простым понятным языком.
Ответить