Экспертная система Delphi.int.ru

Сообщество программистов
Общение, помощь, обмен опытом

Логин:
Пароль:
Регистрация | Забыли пароль?

Delphi.int.ru Expert

Другие разделы портала

Переход к вопросу:

#   

Статистика за сегодня:  


Лучшие эксперты

Подробнее »



Вопрос # 3 256

/ вопрос открыт /

Здравствуйте, уважаемые эксперты!
Задаю предельное значение интересуемого меня параметра

procedure TForm1.FormCreate(Sender: TObject);
 
begin
...
Limit:= 1.4;
...
end;
<code>
 
Потом
 
<code>
procedure TForm1.Timer1Timer(Sender: TObject);
begin
...
// округляю и превращаю в текст полученное из вне значение
Value_String:=FloatToStr(RoundTo(Value, -2));
...
// смотрю, если округленные данные больше Limit,
// то устанавливаю вывод данных в memo красным, а если меньше - черным
if RoundTo(Value, -2)&gt;Limit then  RichEdit1.SelAttributes.Color:=clRed else RichEdit1.SelAttributes.Color:=clBlack;
...
// вывожу данные
Memo1.Lines.Add(Value_String);
...
// принудительно устанавливаю черный цвет вывода данных
RichEdit1.SelAttributes.Color:=clBlack;
...
end;


И все работает корректно, но только в мемо появляется 1.4, так сразу начинаются неясности. То у меня 1.4 в красном, то в черном. В чем моя ошибка подскажите. Может я некорректно сформулировал, тогда скажите, а то немного запутался.

anbaresi Вопрос ожидает решения (принимаются ответы, доступен мини-форум)

Вопрос задал: anbaresi (статус: Посетитель)
Вопрос отправлен: 6 октября 2009, 20:57
Состояние вопроса: открыт, ответов: 1.

Ответ #1. Отвечает эксперт: min@y™

А полученное извне значение какую точность имеет и тип? Откуда оно ваще берётся?
Все грабли из-за неточности представления в двоичном виде чисел с плавающей точкой и работы функций FloatToStr() и RoundTo().
Почитай статью Антона Грагорьева "Неочевидные особенности вещественных чисел", узнаешь много нового.

Ответ отправил: min@y™ (статус: Доктор наук)
Время отправки: 6 октября 2009, 21:43
Оценка за ответ: 5


Мини-форум вопроса

Всего сообщений: 11; последнее сообщение — 8 октября 2009, 17:43; участников в обсуждении: 3.
anbaresi

anbaresi (статус: Посетитель), 7 октября 2009, 17:57 [#1]:

Спасибо за ссылку, почитаю. Может прояснится ситуация.
Немного покопался, попробовал в простом примере - вот его код:
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Math, ComCtrls;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    RichEdit1: TRichEdit;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
  KSV: double; // потому что извне читается double
  KSV_String: string;
  i: integer;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.Button1Click(Sender: TObject);
begin
for i:=1391 to 1410 do
    begin
    KSV:=(i+1)/1000; // типа считали это значение
    KSV_String:=FloatToStr(RoundTo(KSV,-2));
    if (RoundTo(KSV,-2))<1.4 then RichEdit1.SelAttributes.Color:=clRed else RichEdit1.SelAttributes.Color:=clBlack;;
    RichEdit1.Lines.Add(FloatToStr(KSV)+' -> '+KSV_String);
    RichEdit1.SelAttributes.Color:=clBlack;
    end;
 
end;
 
end.
а на картинке результат выполнения. Получается, что 1,4 больше чем 1,4
Блин, а как картинку-то прицепить?
Вадим К

Вадим К (статус: Академик), 7 октября 2009, 18:10 [#2]:

да, 1.4 может быть больше чем 1.4. Святое правило гласит - не сравнивай на равенство вещественные числа.
Для избежания этого есть два пути - первое - сравнение с дельтой. Второй способ - перевести их в целые числа. То есть сравнивать не 1.4, а 14.
Галочка "подтверждения прочтения" - вселенское зло.
min@y™

min@y™ (статус: Доктор наук), 7 октября 2009, 18:23 [#3]:

Ну вот, гляди, написал я тебе примерчик консольный. Посмотри внимательно и всё поймёшь (и статью почитай).
program p3256;
 
{$APPTYPE CONSOLE}
 
uses
  Types, SysUtils, Math;
 
const
  Limit = 1.4;
  CompareResults: array[TValueRelationship] of string = ('less ', 'equal', 'more ');
  Precision = 15; // Точность
  Digits = 3; // Кол-во знаков после запятой
 
var
  Value, Rounded: Double;
  Index: Integer;
 
function CompareFloat(const F1, F2: Double): TValueRelationship;
begin
  if F1 < F2
    then Result:= -1
    else if F1 > F2
           then Result:= +1
           else Result:= 0;
end;
 
begin
  for Index:= 1391 to 1410 do
    begin
      Value:= Index / 1000;
      Rounded:= RoundTo(Value, -2);
      WriteLn('Index: ', Index,
              '; Value ', Value: 2: 3, ' (Rounded to ', Rounded: 2: 3, ') is ',
              CompareResults[CompareFloat(Rounded, Limit)],
              ' than Limit ', Limit: 2: 3);
    end;
 
  ReadLn;  
end.
А вот результат работы проги, скопипастнутый из консоли:
Index: 1391; Value 1.391 (Rounded to 1.390) is less  than Limit 1.400
Index: 1392; Value 1.392 (Rounded to 1.390) is less  than Limit 1.400
Index: 1393; Value 1.393 (Rounded to 1.390) is less  than Limit 1.400
Index: 1394; Value 1.394 (Rounded to 1.390) is less  than Limit 1.400
Index: 1395; Value 1.395 (Rounded to 1.390) is less  than Limit 1.400
Index: 1396; Value 1.396 (Rounded to 1.400) is more  than Limit 1.400
Index: 1397; Value 1.397 (Rounded to 1.400) is more  than Limit 1.400
Index: 1398; Value 1.398 (Rounded to 1.400) is more  than Limit 1.400
Index: 1399; Value 1.399 (Rounded to 1.400) is more  than Limit 1.400
Index: 1400; Value 1.400 (Rounded to 1.400) is more  than Limit 1.400
Index: 1401; Value 1.401 (Rounded to 1.400) is more  than Limit 1.400
Index: 1402; Value 1.402 (Rounded to 1.400) is more  than Limit 1.400
Index: 1403; Value 1.403 (Rounded to 1.400) is more  than Limit 1.400
Index: 1404; Value 1.404 (Rounded to 1.400) is more  than Limit 1.400
Index: 1405; Value 1.405 (Rounded to 1.400) is more  than Limit 1.400
Index: 1406; Value 1.406 (Rounded to 1.410) is more  than Limit 1.400
Index: 1407; Value 1.407 (Rounded to 1.410) is more  than Limit 1.400
Index: 1408; Value 1.408 (Rounded to 1.410) is more  than Limit 1.400
Index: 1409; Value 1.409 (Rounded to 1.410) is more  than Limit 1.400
Index: 1410; Value 1.410 (Rounded to 1.410) is more  than Limit 1.400
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Вадим К

Вадим К (статус: Академик), 7 октября 2009, 18:33 [#4]:

min@y™ , не нужно повторять большую ошибку. Функция CompareFloat имеет неявное сравнение вещественных чисел на равенство.
И если программа возвратит equal, то это будет просто везение.
Галочка "подтверждения прочтения" - вселенское зло.
min@y™

min@y™ (статус: Доктор наук), 7 октября 2009, 18:40 [#5]:

Цитата (Вадим К):


Функция CompareFloat имеет неявное сравнение вещественных чисел на равенство.
И если программа возвратит equal, то это будет просто везение.

Я специально написал эту функцию с неявным результатом "равно", чтобы показать, что в данном случае такого результата НЕ БУДЕТ!
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
Вадим К

Вадим К (статус: Академик), 7 октября 2009, 19:00 [#6]:

даром.
плюс сравнение двух вещественных чисел, которые близки друг к дружке, но получены разными способами всегда непредсказуемо. А то что в примере наблюдается строгое разделение больше/меньше - это легко объяснить. Один аргумент зафиксирован.
Галочка "подтверждения прочтения" - вселенское зло.
min@y™

min@y™ (статус: Доктор наук), 7 октября 2009, 19:30 [#7]:

Так именно это я и хотел показать.
В ответе я написал, что надо сравнивать с заданной погрешностью.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
anbaresi

anbaresi (статус: Посетитель), 7 октября 2009, 19:50 [#8]:

Спасибо всем, буду сравнивать разницу двух значений. Я похоже зациклился и не допер, ну и опыта еще не хватает.
anbaresi

anbaresi (статус: Посетитель), 8 октября 2009, 15:47 [#9]:

в продолжении темы, если сделать так:

if RoundTo(KSV,-2)*100>1.4*100 then RichEdit1.SelAttributes.Color:=clRed else RichEdit1.SelAttributes.Color:=clBlack;

то сравнение опять происходит не так как мне надо и 1,4 опять больше, чем 1,4.

А вот если так:

if Round(RoundTo(KSV,-2)*100)>Round(1.4*100) then RichEdit1.SelAttributes.Color:=clRed else
RichEdit1.SelAttributes.Color:=clBlack;

Прочитал я указанную статью, способов решения там не показывается. И предлагается самому искать метод, в зависимости от того, кто ты - ламер или программист. Вот мне интересно, то что я написал это корректно? Или по-ламерски :)
Вадим К

Вадим К (статус: Академик), 8 октября 2009, 16:02 [#10]:

рекомендую вместо round применять floor. А то round работает весьма интересно (для непосвященных)
round(2.5) будет два
а round (3.5) будет 4, вот так то.
В случае выражения Round(1.4*100) оно может быть разным. например 1399 или 1401
а если ещё 1.4 получается с другой переменной (например с Edit), то возможно что угодно.
Галочка "подтверждения прочтения" - вселенское зло.
min@y™

min@y™ (статус: Доктор наук), 8 октября 2009, 17:43 [#11]:

Путь правильный: гораздо проще сравнивать целые числа. Приходящие дробные данные умножай на 100, округляй (до целого) и сравнивай с числом Limit = 140. RoundTo выкинь оттуда. Если нужна большая точность, умножай на 1000, 10000 и т.д. в зависимости от задачи.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!

Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.

Версия движка: 2.6+ (26.01.2011)
Текущее время: 22 февраля 2025, 11:48
Выполнено за 0.05 сек.