Как избежать проблем округления при сравнении значений валют в Delphi? - PullRequest
3 голосов
/ 08 октября 2008

AFAIK, тип валюты в Delphi Win32 зависит от точности процессора с плавающей запятой. Из-за этого у меня возникают проблемы с округлением при сравнении двух значений Currency и выдаче разных результатов в зависимости от компьютера.

Сейчас я использую функцию SameValue, передавая параметр Epsilon = 0,009, потому что мне нужна только точность до 2 десятичных цифр.

Есть ли лучший способ избежать этой проблемы?

Ответы [ 6 ]

14 голосов
/ 08 октября 2008

Тип валюты в Delphi - это 64-разрядное целое число, масштабированное на 1/10 000; другими словами, его наименьшее приращение эквивалентно 0,0001. Он не подвержен ошибкам точности так же, как код с плавающей запятой.

Однако, если вы умножаете свои числа Валюты на типы с плавающей запятой или делите значения Валюты, округление необходимо выполнить тем или иным способом. FPU управляет этим механизмом (это называется «управляющим словом»). Модуль Math содержит некоторые процедуры, которые управляют этим механизмом, в частности SetRoundMode. Вы можете увидеть эффекты в этой программе:

{$APPTYPE CONSOLE}

uses Math;

var
  x: Currency;
  y: Currency;
begin
  SetRoundMode(rmTruncate);
  x := 1;
  x := x / 6;
  SetRoundMode(rmNearest);
  y := 1;
  y := y / 6;
  Writeln(x = y); // false
  Writeln(x - y); // 0.0001; i.e. 0.1666 vs 0.1667
end.

Возможно, используемая вами сторонняя библиотека устанавливает для управляющего слова другое значение. Возможно, вы захотите установить контрольное слово (то есть режим округления) явно в начальной точке ваших важных вычислений.

Кроме того, если ваши расчеты когда-либо переводятся в обычную плавающую точку, а затем обратно в валюту, все ставки отклонены - слишком сложно для аудита. Убедитесь, что все ваши расчеты в валюте.

12 голосов
/ 08 октября 2008

Нет, валюта не является типом с плавающей запятой. Это десятичное число с фиксированной точностью, реализованное с целочисленной памятью. Его можно точно сравнить, и в нем нет вопросов округления, скажем, Double. Поэтому, если вы видите неточные значения в ваших переменных валюты, проблема не в самом типе валюты, а в том, что вы вкладываете в него. Скорее всего, у вас есть вычисления с плавающей точкой где-то еще в вашем коде. Поскольку вы не показываете этот код, трудно помочь в этом вопросе. Но, вообще говоря, решение состоит в том, чтобы округлить числа с плавающей запятой до правильной точности перед сохранением в переменной Currency, а не проводить неточное сравнение с переменными Currency.

1 голос
/ 09 ноября 2011

Более быстрый и безопасный способ сравнения двух значений currency, безусловно, заключается в сопоставлении переменных с их внутренним представлением Int64:

function CompCurrency(var A,B: currency): Int64;
var A64: Int64 absolute A;
    B64: Int64 absolute B;
begin
  result := A64-B64;
end;

Это позволит избежать любой ошибки округления при сравнении (работа с * 10000 целочисленными значениями) и будет быстрее, чем реализация на основе FPU по умолчанию (особенно в 64-битном компиляторе XE2).

См. эту статью для получения дополнительной информации.

0 голосов
/ 06 мая 2009

См. Тему:

D7 / DUnit: все тесты CheckEquals (Валюта, Валюта) неожиданно завершаются неудачей ...

https://forums.codegear.com/thread.jspa?threadID=16288

Похоже, что изменение на наших рабочих станциях привело к сбою сравнения валют. Мы не нашли причину, но на двух компьютерах под управлением Windows 2000 SP4 и независимо от версии gds32.dll (InterBase 7.5.1 или 2007) и Delphi (7 и 2009) эта строка

TIBDataBase.Create(nil);

изменяет значение контрольного слова 8087 с $ 1372 на $ 1272.

И все сравнения валют в модульных тестах завершатся неудачно с такими забавными сообщениями, как

Expected: <12.34> - Found: <12.34>

gds32.dll не был изменен, поэтому я предполагаю, что в этой библиотеке есть зависимость от сторонней библиотеки dll, которая изменяет управляющее слово.

0 голосов
/ 08 октября 2008

Если ваша ситуация похожа на мою, вам может пригодиться этот подход. Я работаю в основном в заработной плате. Если у бизнеса есть, например, 3 отдела, и он хочет равномерно распределить расходы на одного сотрудника между этими тремя отделами, то во многих случаях возникают проблемы округления.

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

0 голосов
/ 08 октября 2008

Чтобы избежать возможных проблем с округлением валюты в Delphi, используйте 4 десятичных знака.

Это гарантирует, что у вас никогда не возникнет проблем с округлением при выполнении расчетов с очень небольшими суммами.

"Been there. Done That. Written the unit tests."

...