自己动手写C语言格式化输出函数(三)
上接《自己动手写C语言格式化输出函数(二)》。
八、格式化浮点数(有关浮点数的数据定义和底层的数据转换函数见《自己动手写C语言浮点数转换字符串函数》一文)。
2 static void GetFloatRec(FormatRec *rec, INT flag, FloatRec *fRec)
3 {
4 EXTENDED value;
5 if (rec->precision < 0)
6 rec->precision = F_DEFDECIMALS;
7 else if (rec->precision > F_MAXDECIMALS)
8 rec->precision = F_MAXDECIMALS;
9 if (rec->type == TYPE_LLONG)
10 {
11 value = *(PEXTENDED)rec->param;
12 rec->param += TS_EXTENDED;
13 }
14 else
15 {
16 value = *(double*)rec->param;
17 rec->param += TS_DOUBLE;
18 }
19 switch (flag)
20 {
21 case 0: // %f
22 FloatResolve(&value, F_MAXPRECISION, rec->precision, fRec);
23 break;
24 case 1: // %e or %E
25 FloatResolve(&value, rec->precision + 1, 9999, fRec);
26 break;
27 case 2: // %g or %G
28 FloatResolve(&value, rec->precision, 9999, fRec);
29 }
30 if (fRec->negative)
31 rec->negative = -1;
32 }
33
34 // 格式化小数字串。参数:缓冲区,格式记录,数字串,数字串长度。返回缓冲区尾偏移
35 static LPSTR FormatDecimalA(LPSTR buffer, FormatRec *rec, LPCSTR str, INT strLen)
36 {
37 LPSTR p = buffer;
38 INT spaces = rec->width - strLen;
39 if (rec->negative)
40 {
41 spaces --;
42 if (rec->left || rec->zero)
43 *p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS);
44 }
45 if (rec->left == FALSE)
46 {
47 if (spaces > 0)
48 {
49 memset(p, rec->zero? CHAR_ZERO : CHAR_SPACE, spaces);
50 p += spaces;
51 }
52 if (rec->negative && !rec->zero)
53 *p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS);
54 }
55 memcpy(p, str, strLen);
56 p += strLen;
57 if (rec->left && spaces > 0)
58 {
59 memset(p, CHAR_SPACE, spaces);
60 p += spaces;
61 }
62 return p;
63 }
64
65 #define F_MAXEXPONENT 45
66 #define F_MINEXPONENT -45
67
68 // 输出指数字符串到buffer,返回指数字符串长度
69 INT PutExponent(LPSTR buffer, CONST FloatRec *rec)
70 {
71 LPSTR p = buffer;
72 INT e, exp = rec->digits[0]? rec->exponent - 1 : 0;
73 *p ++ = rec->negative & 0x80? 'E' : 'e';
74 if (exp < 0)
75 {
76 exp = -exp;
77 *p ++ = '-';
78 }
79 else *p ++ = '+';
80 if ((e = (exp / 1000)) != 0)
81 {
82 *p ++ = e + 0x30;
83 exp %= 1000;
84 }
85 *p ++ = exp / 100 + 0x30;
86 exp %= 100;
87 *(PUSHORT)p = (((exp % 10) << 8) | (exp / 10)) + 0x3030;
88 return (INT)(p - buffer + 2);
89 }
90
91 // 按浮点数记录信息转换为指数格式数字串。
92 // 参数:缓冲区,浮点数记录,转换精度,是否强制小数位
93 static INT FloatExponentA(LPSTR buffer, CONST FloatRec *rec, INT precision, BOOL decPoint)
94 {
95 LPSTR p = buffer;
96 LPCSTR digits = rec->digits;
97 if (rec->exponent > 0 || *digits)
98 *p ++ = *digits ++;
99 else
100 *p ++ = '0';
101 if (precision > 0 || decPoint)
102 {
103 for (*p ++ = '.'; precision > 0 && *digits; *p ++ = *digits ++, precision --);
104 for (; precision > 0; *p ++ = '0', precision --);
105 }
106 p += PutExponent(p, rec);
107 return (INT)(p - buffer);
108 }
109
110 // 按浮点数记录信息转换为小数格式数字串。
111 // 参数:缓冲区,浮点数记录,转换精度,是否强制小数位
112 static INT FloatDecimalA(LPSTR buffer, CONST FloatRec *rec, INT precision, BOOL decPoint)
113 {
114 LPSTR p;
115 LPCSTR digits;
116 INT exp = rec->exponent;
117 if (exp > F_MAXEXPONENT || exp < F_MINEXPONENT)
118 return FloatExponentA(buffer, rec, precision, decPoint);
119 p = buffer;
120 digits = rec->digits;
121 if (exp > 0)
122 {
123 for (; exp > 0 && *digits; *p ++ = *digits ++, exp --);
124 for (; exp > 0; *p ++ = '0', exp --);
125 if (decPoint || precision > 0)
126 *p ++ = '.';
127 }
128 else
129 {
130 exp = -exp;
131 precision -= exp;
132 if (precision < 0)
133 {
134 exp += precision;
135 precision = 0;
136 }
137 *p ++ = '0';
138 if (exp > 0 || decPoint || precision > 0)
139 {
140 *p ++ = '.';
141 for (; exp > 0; *p ++ = '0', exp --);
142 }
143 }
144 for (; precision > 0 && *digits; *p ++ = *digits ++, precision --);
145 for (; precision > 0; *p ++ = '0', precision --);
146 return (INT)(p - buffer);
147 }
148
149 // 浮点数格式化为小数串。参数:缓冲区,格式记录。返回缓冲区尾偏移
150 static LPSTR FormatFloatFA(LPSTR buffer, FormatRec *rec)
151 {
152 FloatRec fRec;
153 INT len;
154 CHAR tmp[F_MAXDECIMALS+48];
155
156 GetFloatRec(rec, 0, &fRec);
157 if (fRec.digits[0] > '9') // nan or inf
158 return FormatDecimalA(buffer, rec, fRec.digits, 3);
159 len = FloatDecimalA(tmp, &fRec, rec->precision, rec->decimals);
160 return FormatDecimalA(buffer, rec, tmp, len);
161 }
162
163 // 浮点数格式化为指数串。参数:缓冲区,格式记录。返回缓冲区尾偏移
164 static LPSTR FormatFloatEA(LPSTR buffer, FormatRec *rec, CHAR expChar)
165 {
166 FloatRec fRec;
167 INT len;
168 CHAR tmp[F_MAXDECIMALS+8];
169
170 GetFloatRec(rec, 1, &fRec);
171 if (fRec.digits[0] > '9') // nan or inf
172 return FormatDecimalA(buffer, rec, fRec.digits, 3);
173 if (expChar == 'E')
174 fRec.negative |= 0x80; // 高位置1,大写
175 len = FloatExponentA(tmp, &fRec, rec->precision, rec->decimals);
176 return FormatDecimalA(buffer, rec, tmp, len);
177 }
178
179 // 浮点数格式化为小数串或者指数串。参数:缓冲区,格式记录。返回缓冲区尾偏移
180 static LPSTR FormatFloatGA(LPSTR buffer, FormatRec *rec, CHAR expChar)
181 {
182 FloatRec fRec;
183 INT len, precision;
184 CHAR tmp[F_MAXDECIMALS+48];
185
186 GetFloatRec(rec, 2, &fRec);
187 if (fRec.digits[0] > '9') // nan or inf
188 return FormatDecimalA(buffer, rec, fRec.digits, 3);
189 if (expChar == 'G')
190 fRec.negative |= 0x80; // 高位置1,大写
191 if (fRec.exponent > rec->precision || fRec.exponent < -3)
192 {
193 precision = rec->decimals? rec->precision - 1 : lstrlenA(fRec.digits) - 1;
194 len = FloatExponentA(tmp, &fRec, precision, rec->decimals);
195 }
196 else
197 {
198 precision = rec->decimals? rec->precision - fRec.exponent : lstrlenA(fRec.digits) - fRec.exponent;
199 if (precision < 0) precision = 0;
200 len = FloatDecimalA(tmp, &fRec, precision, rec->decimals);
201 }
202
203 return FormatDecimalA(buffer, rec, tmp, len);
204 }
在sprintfA函数中,浮点数的格式化是最复杂的。浮点数有2种表现形式,即小数形式和指数形式,分别用"%f"和"%e"格式表示,另外还有个"%g"格式,这是个自动格式,即sprintfA通过分析后,自行决定采用哪种形式。
在以小数形式的格式化中,对数据的格式化有个极限长度,不然,在扩展精度浮点数下,有些浮点数长度可达到近5000位,即使是双精度浮点数,最高长度也达300多。在printf系列函数中,这个极限长度随编译器不同而不同,有的将这个值定为100,有的定为单精度浮点数的最大表现形式,即38等,我在这里把它定为了正负45位,当数据超过这个极限长度,就自动采用指数形式来格式化数据了。
在介绍sprintfA数据定义时就说过,由于sprintfA的可变参数部分没有参数原型供编译器对照,所以在输入浮点数参数时要注意与格式字符串中对应的浮点数精度匹配,32位编译器的浮点数缺省精度是64位双精度数,即使你给的参数变量是个单精度数,也会扩展为双精度数,如果参数变量是long double,而你使用的编译器支持80位扩展精度浮点数时,传递的是80位扩展精度数,否则也是双精度数,如果你给出一个整数,编译器是不会自动转换的。如果你在参数位置输入的是常数就更应该注意了,123,123L,123.0f,123.0,123.0L这几种常数形式是不同的(L也可是小写),分别是整数,长整数,单精度浮点数,双精度浮点数,扩展精度浮点数(如果编译器不支持,也是双精度数)。所以,在32位及以上编译器中格式%f和%lf是等同的,自然,在不支持扩展精度浮点数的编译器中,%llf(%Lf)也等同于%f。
本文格式化浮点数时用到的FloatResolve函数以及有关数据定义见《自己动手写C语言浮点数转换字符串函数》。
有关sprintfA函数的介绍就全部完毕。文章代码没进行严格的测试。
声明:本文代码主要供学习使用,如作其它用途,出问题慨不负责。
水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com