Delphi FormatFloat другое поведение (переменная и набор данных)

Я обнаружил странное поведение функции FormatFloat в Delphi. Позвольте мне показать тематическое исследование.

значение для преобразования: 129809,495
желаемый форматированный вывод: 129 809,50

Случай 1: преобразование из строки

var str: string;
str := '129809.495';  
str := FormatFloat(',0.00', StrToFloat(str));  
// output is 129,809.50 = CORRECT  

Случай 2: преобразование из двойной переменной

var number: double;
str: string;
val := 129809.495;  
str := FormatFloat(',0.00', val);  
// output is 129,809.50 = CORRECT

Случай 3: Преобразование из поля набора данных

*it's too complex to write here, let me just explain*  
Basically the formatted output of FormatFloat(',0.00', Dataset.Field[0].AsFloat);
always resulted in 129,809.49 == WRONG

Я тестировал это поведение с использованием SQL Server 2008 и Firebird 1.5.
Используемые компоненты - это ADO-компоненты и UniDAC-компоненты (от DevArt), и все они ведут себя одинаково.

Я пытался сделать это:

  • FormatFloat (', 0.00', Dataset.Field [0].AsFloat);
  • FormatFloat (', 0,00', StrToFloat (Dataset.Field [0].AsString));
  • val: = Dataset.Field [0].AsFloat; FormatFloat (', 0,00', val);
  • str: = Dataset.Field [0].AsString; FormatFloat (', 0,00', StrToFloat (str));
  • val: = StrToFloat (Dataset.Field [0].AsString); FormatFloat (', 0,00', val);

Но все это привело к тому же неправильному преобразованию.49 вместо.50

1 способ, который работает

val := StrToFloat(Dataset.Field[0].AsString); 
FormatFloat(',0.00', val);  

У кого-нибудь есть решение для такого поведения? Потому что было бы слишком много работы, чтобы принудительно преобразовать StrToFloat и затем переформатировать переменную / вывод. И этот обходной путь не может быть применен к сторонним компонентам, которые использовали FormatFloat

Любая помощь приветствуется. Спасибо

2 ответа

Решение

Похоже, это связано с некоторыми различиями в точности между Double и Extended. На самом деле я не могу подтвердить ваше замечание о том, что дело 2 верно. Возможно, потому что вы объявляете переменную number как double, но затем используете переменную val неизвестного типа.

Во всяком случае, следующий код

var
  d: Double;
  e: Extended;
begin
  d := 129809.495;
  e := 129809.495;
  Writeln(FormatFloat(',0.00', d));
  Writeln(FormatFloat(',0.00', e));
  e := d;
  Writeln(FormatFloat(',0.00', e));
  e := 129809.495;
  d := e;
  Writeln(FormatFloat(',0.00', d));
end;

скомпилированный с XE6 выдает следующий вывод:

129,809.49
129,809.50
129,809.49
129,809.49

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

Сделайте модуль MyUnit вот таким:

unit MyUnit;

interface
uses
  System.SysUtils;

function FormatFloat(const Format: string; Value: Extended): string;

implementation

function FormatFloat(const Format: string; Value: Extended): string;
begin
  Value := StrToFloat(Value.ToString);  
  Result:=System.SysUtils.FormatFloat(',0.00', Value);
end;

end.

и вставьте его на последнем месте предложения Uses. Любой вызов функции FormatFloat вызовет вашу функцию.

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

var
  val: Single;

begin
  val:=129809.495;
  ShowMessage(FormatFloat(',0.00', val));

с моей функцией и rseult был val 129809.49, потому что переменная, переданная в функцию, была 129809.4921875 после преобразования из Single в Extended. Поэтому для отладки записи в файл журнала каждого вызова функции может быть хорошей идеей.

Другие вопросы по тегам