Николай Чуносов дата публикации 12-12-2006 07:55 Mathcad-подобная функция форматирования вещественных чисел.
При написании приложения для решения научных задач я столкнулся с проблемой, что предоставляемые Delphi функции преобразования вещественного числа в строку не устраивают меня по тем или иным причинам. В Matcad’е имеется стиль форматирования, который мне казался очень удобным и хотелось использовать подобное форматирование в своем приложении.
Стиль управляется тремя параметрами:
- Число знаков после запятой - Digits
- Флаг заполнения нулями недостющих знаков – ZeroFill (0.2 –> 0.200 при Digits = 3)
- Экспонециальный порог – ExpThreshold. Если число по модулю больше 10^ExpThreshold или меньше 10^(–ExpThreshold), то оно представляется в экспоненциальном формате, иначе в формате с фиксированной запятой.
Рассмотрим всем известные стандартные функции форматирования вещественных чисел, и чем они не подходят для решения поставленной задачи.
- FloatToStr
- однозначно не подходит, т.к. не имеет вообще никаких управляющих параметров.
- FormatFloat
- поддерживает управляющую строку, может отображать число либо в фиксированном, либо в экспоненциальном виде, однако сама не решает в каком – не подходит.
- FloatToStrF / FloatToText
-
- Формат ffNumber
- использует фиксированное представление и разделитель тысяч. Не помню случая, чтобы в научных программах или статьях использовался разделитель тысяч. Это больше для бухгалтерии.
- Формат ffCurrency
- тоже, что и ffNumber, но добавляет к строке символ валюты, руководствуясь настройками системной локали или структурой TFormatSettings.
Следующие два формата наиболее интересны для нашей цели:
- ffExponent.
- Параметр Precision функции FloatToStrF задает число цифр в мантиссе, одна в целой части, остальные в дробной. Лишние цифры входного значения отбрасываются, в этом случае последняя ставшаяся в дробной части цифра округляется. Параметр Digits задает число знаков в экспоненте. Допустимые значения 1 – 4, все остальные работают как 1. Если степень экспоненты положительная, то перед ней пишется знак "+".
0.00005 –> 5.00000E-005 (Precision = 6, Digits = 3)
123456789 –> 1.235E+8 (Precision = 4, Digits = 1)
- ffFixed.
- Параметр Digits задает число знаков после запятой. Недостающие знаки заполняются нулями. Параметр Precision задает число значащих цифр в результирующей строке. Если целая часть входного значения содержит больше цифр, чем задано в Precision, то значение форматируется в экспоненциальной форме (только, в отличие от ffExponent, почему-то не добавляется знак "+", если степень экспоненты положительная).
1234.56789 –> 1234,57 (Precision = 10, Digits = 2)
1234.56789 –> 1.23E03 (Precision = 3, Digits = 2)
С одной стороны это работает подобно требуемому параметру ExpThreshold, но с другой стороны даже если входное значение содержит в дробной части Digits и более цифр, все равно в результирующую строку будет выведено не более Precision значащих цифр:
1234.56789 -> 1234.5700 (Precision = 6, Digits = 4)
- ffGeneral.
- С этим форматом функция выводит Precision значащих цифр, обрезания знаков после запятой не производится, “добивания” нулями до Digits- тожe не производится. Параметр Digits тут бесполезен, пока не используется экспоненциальный формат. Экспоненциальное представление применяется если входное значение меньше 0.00001, либо, как и в случае ffFixed, если целая часть содержит более Precision цифр.
Таким образом мы имеем формат, который умеет отображать значение в экспоненциальной форме - ffExponent, и формат, отображающий значение в фиксированной форме, с заданным числом знаков после запятой, и заполнением нулями недостающих знаков – ffFixed, если установить ему максимальное значение Precision (18 для типа Extended). А вот когда какой формат применять придется решать самостоятельно, анализируя входное значение.
Сделать это не сложно, стоит только взглянуть на код. Это будет проще всяких объяснений.
function FormatValue(Value: Extended; ExpTheshold, Digits: Byte; ZeroFill: Boolean): String;
var
Min, Max: Extended;
i, j: Integer;
begin
if IsNaN(Value) then
begin
Result := 'NaN';
Exit;
end;
if IsInfinite(Value) then
begin
if Sign(Value) = -1
then Result := '-Inf'
else Result := 'Inf';
Exit;
end;
if Value = 0 then
begin
if ZeroFill
then Result := FloatToStrF(Value, ffFixed, 18, Digits)
else Result := FloatToStrF(Value, ffGeneral, 18, Digits);
Exit;
end;
case ExpTheshold of
0: begin Max := 1e00; Min := 1e-00; end;
1: begin Max := 1e01; Min := 1e-01; end;
2: begin Max := 1e02; Min := 1e-02; end;
3: begin Max := 1e03; Min := 1e-03; end;
4: begin Max := 1e04; Min := 1e-04; end;
5: begin Max := 1e05; Min := 1e-05; end;
6: begin Max := 1e06; Min := 1e-06; end;
7: begin Max := 1e07; Min := 1e-07; end;
8: begin Max := 1e08; Min := 1e-08; end;
9: begin Max := 1e09; Min := 1e-09; end;
10: begin Max := 1e10; Min := 1e-10; end;
11: begin Max := 1e11; Min := 1e-11; end;
12: begin Max := 1e12; Min := 1e-12; end;
13: begin Max := 1e13; Min := 1e-13; end;
14: begin Max := 1e14; Min := 1e-14; end;
15: begin Max := 1e15; Min := 1e-15; end;
16: begin Max := 1e16; Min := 1e-16; end;
17: begin Max := 1e17; Min := 1e-17; end;
else begin Max := 1e18; Min := 1e-18; end;
end;
if Value > 0 then
if (Value < Min) or (Value > Max)
then Result := FloatToStrF(Value, ffExponent, 1+Digits, 1)
else Result := FloatToStrF(Value, ffFixed, 18, Digits)
else
if (Value > -Min) or (Value < -Max)
then Result := FloatToStrF(Value, ffExponent, 1+Digits, 1)
else Result := FloatToStrF(Value, ffFixed, 18, Digits);
if not ZeroFill then
begin
i := Pos('E', Result)-1;
if i = -1 then
begin
i := Length(Result);
while i > 0 do
if Result[i] in ['0',',','.'] then Dec(i)
else begin
SetLength(Result, i);
Break;
end;
end
else
begin
j := i;
while j > 0 do
begin
if Result[j] in ['0',',','.'] then Dec(j)
else begin
Result := Copy(Result, 1, j) + Copy(Result, i+1, 256);
Break;
end;
end;
end;
end;
end;
Итак, если число удовлетворяет условию 10^(–ExpThreshold) >= |Value| >= 10^ExpThreshold, то для его преобразования в строку используется формат ffFixed иначе ffExponent. Я использовал отдельное сравнение для положительного и отрицательного значения, т.к. мне не хотелось несколько раз вызывать функцию Abs.
Т.к. Precision при использовании экспоненциального формата задает общее число знаков в мантиссе и из них один всегда перед запятой, то нам нужно использовать Precision = Digits + 1.
Реализация работы флага ZeroFill уже заложена в формате ffFixed, осталось реализовать его отсутствие. Если флаг ZeroFill не установлен, то удаление нулей, происходит прямо из результирующей строки.
Получилась несложная функция.
[Вещественные числа] [Форматы представления данных]
Обсуждение материала [ 13-12-2006 14:43 ] 4 сообщения |