четверг, 19 февраля 2009 г.

Действительные типы данных в Delphi

Вроде бы понятная тема, которую в справке можно найти в течении нескольких секунд. Все прекрасно знают, что действительные числа представляются с фиксированным числом бит в дробной части. Из-за этого представление с плавающей запятой оказывается несколько неудобным для программ, в которых сохраняется и выводится фиксированное число десятичных разрядов численных значений. В программах, работающим с "живыми" деньгами вопрос точности стоит особо остро.

Но все же иногда об этом не то чтобы забывают, но неприятные сюрпризы ловят.
К примеру, как думаете, что вернет данный код:

var
  V: Extended;
begin
  V := 9.55;
  V := Trunc(V * 100)/100;
  ShowMessage(FloatToStr(V));
end

Правильно! 9.55

Ну а если так?
var
  V: Extended;
begin
  V := 9.15;
  V := Trunc(V * 100)/100;
  ShowMessage(FloatToStr(V));
end

9.15 ?! Ага, конечно... 9.14 выйдет.

Немного поэкспирементируем

var
  V: Double;
begin
  V := 9.15;
  V := Trunc(V * 100)/100;
end

И получим правильный ответ. Но для чистоты эксперимента прогоним небольшой тест
var
  V, T: Double;
  Errors: Integer;
begin
  V      := 0;
  Errors := 0;

  while V <= 100 do
  begin
    T := Trunc(V * 100) / 100;
    if Abs(T - V) > 1/MaxDouble then
      Inc(Errors);
    V := V + 0.01;
  end;

  ShowMessage(IntToStr(Errors));
end

Получим кол-во ошибок равное 9984, для типа переменных V и T Extended 9882...

Шеф, усе пропало!


Однако, для частичного решения этой проблемы в Delphi определены два формата с фиксированной запятой. Тип Comp (computational - вычислительный) содержит только целые числа в диапазоне от -263+1 до 263-1, что примерно соответствует диапазону от -9,2х1018 до 9,2х1018. При программировании операций с американской валютой разработчикам обычно приходится искать естественный способ записи денежных сумм, в котором целая часть числа определяет количество долларов, дробная - центов. Если такие значения записывать в переменные типа Comp, придется представлять их в виде целого числа центов. В этом случае следует умножать значение на 100 для обращения центов в доллары, а затем делить на 100, чтобы снова получить центы.

Этих забот можно избежать, если воспользоваться типом Currency. В этом случае задачу выбора масштаба возьмет на себя компилятор. Физически значения Currency записываются в память того же объема, что и Comp, как целые числа, однако компилятор не забывает вовремя разделить значение на 10 000 (не на 100!) для его приведения в соответствие с денежным знаком и умножить на 10 000 перед записью в память. Это обеспечивает абсолютную точность в четыре десятичных знака после запятой.


Вот так вот.

Ссылка на статью, используемую в этой публикации