Приветствую уважаемых гуру, и, вообще, всех участников данного форума, который уже очень-очень много раз спасал в трудных ситуациях. Ну, и, как говорится "никогда не было, и, вот, опять...". Снова заносит меня в области, где Гугл не помогает (хоть, вроде, меня там пока не забанили, и искать честно пытался).
Суть вопроса.
Все мы знаем, что переменной типа Currency (с фиксированной точностью в 4 знака после запятой) можно присвоить значение и с бо́льшим количеством знаков, и никакой ошибки не будет. Если, конечно, не считать того, что в этой переменной будет храниться уже не то число, которое мы присваивали.
Так, в частности, код, представленный ниже, отработает без ошибок, без варнингов и хинтов:
Код:
var
Значение: Currency;
begin
Значение := 0.56787;
end;
Однако, в переменной "Значение" будет храниться в точности 0,5679.
В настройках проекта у меня стоят галочки проверки переполнения и выхода за границы диапазона, но что толку...
Существует ли какая-то возможность (например, путем установки каких-либо директив компилятора, или кто его знает, как...) отловить момент потери точности при присвоении значения переменной типа Currency?
Может быть, как-то можно переопределить оператор присваивания для конкретного случая (если слева Currency, а справа Single, Real, Double или Extended)?
Собственно, вопрос сформулирован. Дальше уже можно и не читать (ниже — просто описание моих тщетных поисков путей решения).
(Предвижу вопрос, типа, "... а нафига оно тебе?". Отвечу сразу: во-первых, академический интерес, во-вторых, приложение связано с подсчетом финансовых показателей, и если из-за потерь точности где-то вылезут какие-то ошибки, то я хотел бы иметь возможность, хотя бы логировать те моменты, где происходит эта потеря, чтобы, если что, потом знать, где копать).
Григорьева А. Б. "О чём не пишут в книгах по Delphi" (глава 3. "Подводные камни") читал внимательно. Но после прочтения стало не спокойнее, а, напротив, еще тревожнее на душе, поскольку до этого я и сам знал, что с плавающей запятой все сложно, а тут еще и осознал, "размеры бедствия всерьез".
Что же касается ответа на вопрос "Как же быть?", то он пишет так:
Цитата:
3.2.13. Методы решения проблем
Подведем итоги сказанному. Значения, которые мы получаем, могут отличаться от ожидаемых, даже если речь идет о простом присваивании. Во многих случаях (например, в научных расчетах) это несущественно, т. к. сам метод расчета дает еще большую погрешность. Проблемы начинаются там, где мы хотим вывести число на экран или сравнить его с другим. Универсальных рецептов на все случаи жизни не существует, но во многих ситуациях помогают следующие советы:
□ Если ваша задача — просто получить "красивое" представление числа на экране, то функцию FloatToStr заменяйте на ее более мощный аналог FloatToStrF или на функцию Format — они позволяют указать желаемое количество символов после точки.
□ Сравнение вещественных чисел следует выполнять с учетом погрешности, т. е. вместо if а = b … писать if Abs(а — b) < Ерs …, где Eps — некоторая величина, задающая допустимую погрешность (в модуле Math, начиная с Delphi 6, существует функция SameValue, с помощью которой это же условие можно записать как if SameValue(a, b, Eps) …).
□ Для денежных расчетов следует выбирать тип Currency, реализующий число с фиксированной, а не плавающей, десятичной точкой. Отметим также, что не следует пытаться решить проблему неточного представления числа (0,100000001490116 вместо 0,1) с помощью функции RoundTo, поскольку эта функция не может обеспечить точность бо́льшую, чем точность аппаратного представления вещественных чисел.
|
... но, как бы... от такого "доброго совета" не легче ни разу (от слова "совсем").
Ну, вот, использую я Currency, и точно знаю, что у меня не плавающая, а фиксированная точка (железно 4 знака, и "абсолютная точность"). Но, я даже не могу никак отследить момент типа:
Код:
var
Значение: Currency;
begin
Значение := Что_то_там / Что_то_ещё;
end;
и даже знать не знаю, сколько я потерял при таком присвоении.
Пытался запилить свой класс TCurrencyExt, в котором, среди прочих, реализовал такой метод:
Код:
procedure TCurrencyExt.SetValue(const AValue: Extended);
begin
FValue := AValue;
if AValue <> FValue then АЛАРМ_АЛАРМ_БИТЬ_ВО_ВСЕ_КОЛОКОЛА_ПОТЕРЯ_ТОЧНОСТИ!!!11;
end;
Ну, ежу понятно, что если вызывать его так:
Код:
var
...
CExt: TCurrencyExt;
...
implementation
procedure TViewMain.FormCreate(Sender: TObject);
var
Значение: Currency;
begin
CExt := TCurrencyExt.Create(...);
Значение := 0.56787;
CExt.SetValue(Значение);
end;
... то толку с этого как с козла молока. Единственное, когда это может спасти, это, например, если:
Код:
var
...
CExt: TCurrencyExt;
...
implementation
procedure TViewMain.FormCreate(Sender: TObject);
begin
CExt := TCurrencyExt.Create(...);
CExt.SetValue(0.56787);
end;
или
Код:
var
...
CExt: TCurrencyExt;
...
implementation
procedure TViewMain.FormCreate(Sender: TObject);
begin
CExt := TCurrencyExt.Create(...);
CExt.SetValue(StrToFloat(Значение_какого_нибудь_там_Edit_а));
end;
Помогите, пожалуйста, если кто-нибудь может...