Форум по программированию Delphi Sources

 



Вернуться   Форум по программированию Delphi Sources > Все о Delphi > [ "Начинающим" ]
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #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/)

Последний раз редактировалось mxustin, 19.10.2019 в 21:11.
Ответить с цитированием
  #2  
Старый 22.10.2019, 11:02
Аватар для Страдалецъ
Страдалецъ Страдалецъ вне форума
Гуру
 
Регистрация: 09.03.2009
Адрес: На курорте, из окна вижу теплое Баренцево море. Бррр.
Сообщения: 4,714
Репутация: 52347
По умолчанию

Интересная конечно теория, но справка говорит что данный формат может содержать только 4 знака после запятой. Может правильнее взять Single или Double?
__________________
Жизнь такова какова она есть и больше никакова.
Помогаю за спасибо.
Ответить с цитированием
  #3  
Старый 22.10.2019, 22:51
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 7,502
Версия Delphi: 7, XE3, 10.2
Репутация: 49088
По умолчанию

Напрямую отследить это не получится, т.к. здесь нет переполнения или его то подобного. Тут происходит приведение совместимых типов. Т.е. если тебе надо больше чем 4 цифры после точки, то надо писать свой тип.
Если по простому. то можно просто сделать свою арифметику, например, на основе Int64. Пусть тебе надо 6 цифр после точки. Тогда для при присвоении значения умножаешь его на 1000000, а при извлечении делишь на то же значение. Тогда вся остальная арифметика остается такой же, просто по факту происходит переход к полностью целочисленной арифметике.
Более сложный вариант - создание класса в котором целая и дробна части хранятся в разных целочисленных полях. Ну и реализация всей арифметике на тебе.
Ответить с цитированием
Ответ



Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 11:45.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources", 2004-2019

ВКонтакте   Facebook   Twitter