|
Вопрос # 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)>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 (статус: Посетитель)
Вопрос отправлен: 6 октября 2009, 20:57
Состояние вопроса: открыт, ответов: 1.
|
Ответ #1. Отвечает эксперт: min@y™
А полученное извне значение какую точность имеет и тип? Откуда оно ваще берётся?
Все грабли из-за неточности представления в двоичном виде чисел с плавающей точкой и работы функций FloatToStr() и RoundTo().
Почитай статью Антона Грагорьева "Неочевидные особенности вещественных чисел", узнаешь много нового.
 |
Ответ отправил: min@y™ (статус: Доктор наук)
Время отправки: 6 октября 2009, 21:43
Оценка за ответ: 5
|
Мини-форум вопроса
Всего сообщений: 11; последнее сообщение — 8 октября 2009, 17:43; участников в обсуждении: 3.
|
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™ (статус: Доктор наук), 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™ (статус: Доктор наук), 7 октября 2009, 18:40 [#5]:
Цитата (Вадим К):
Функция CompareFloat имеет неявное сравнение вещественных чисел на равенство.
И если программа возвратит equal, то это будет просто везение.
Я специально написал эту функцию с неявным результатом "равно", чтобы показать, что в данном случае такого результата НЕ БУДЕТ!
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
|
|
Вадим К (статус: Академик), 7 октября 2009, 19:00 [#6]:
даром.
плюс сравнение двух вещественных чисел, которые близки друг к дружке, но получены разными способами всегда непредсказуемо. А то что в примере наблюдается строгое разделение больше/меньше - это легко объяснить. Один аргумент зафиксирован.
Галочка "подтверждения прочтения" - вселенское зло.
|
|
min@y™ (статус: Доктор наук), 7 октября 2009, 19:30 [#7]:
Так именно это я и хотел показать.
В ответе я написал, что надо сравнивать с заданной погрешностью.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
|
|
anbaresi (статус: Посетитель), 7 октября 2009, 19:50 [#8]:
Спасибо всем, буду сравнивать разницу двух значений. Я похоже зациклился и не допер, ну и опыта еще не хватает.
|
|
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™ (статус: Доктор наук), 8 октября 2009, 17:43 [#11]:
Путь правильный: гораздо проще сравнивать целые числа. Приходящие дробные данные умножай на 100, округляй (до целого) и сравнивай с числом Limit = 140. RoundTo выкинь оттуда. Если нужна большая точность, умножай на 1000, 10000 и т.д. в зависимости от задачи.
Делаю лабы и курсачи по Delphi и Turbo Pascal. За ПИВО! Пишите в личку, а лучше в аську. А ещё лучше - звоните в скайп!
|
Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.
|