Показать сообщение отдельно
  #1  
Старый 19.10.2019, 21:07
mxustin mxustin вне форума
Прохожий
 
Регистрация: 12.09.2015
Адрес: http://pbrng.16mb.com/home/
Сообщения: 18
Версия Delphi: RAD Studio XE8
Репутация: 10
Вопрос Отследить факт потери точности при присвоении значения переменной типа Currency



Приветствую уважаемых гуру, и, вообще, всех участников данного форума, который уже очень-очень много раз спасал в трудных ситуациях. Ну, и, как говорится "никогда не было, и, вот, опять...". Снова заносит меня в области, где Гугл не помогает (хоть, вроде, меня там пока не забанили, и искать честно пытался).

Суть вопроса.

Все мы знаем, что переменной типа 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;

Помогите, пожалуйста, если кто-нибудь может...
__________________
Разрабатываю генератор случайных чисел на основе пинга (информация — на сайте http://pbrng.16mb.com/)
Ответить с цитированием