上接《自己动手写C语言格式化输出函数(二)》、《自己动手写C语言格式化输出函数(一)》。
八、格式化浮点数(有关浮点数的数据定义和底层的数据转换函数见《自己动手写C语言浮点数转换字符串函数》一文)。
// 转换浮点数信息到浮点数记录fRec。参数:格式记录,格式方式标记,浮点数记录
static void GetFloatRec(FormatRec *rec, INT flag, FloatRec *fRec)
{
EXTENDED value;
if (rec->precision < 0)
rec->precision = F_DEFDECIMALS;
else if (rec->precision > F_MAXDECIMALS)
rec->precision = F_MAXDECIMALS;
if (rec->type == TYPE_LLONG)
{
value = *(PEXTENDED)rec->param;
rec->param += TS_EXTENDED;
}
else
{
value = *(double*)rec->param;
rec->param += TS_DOUBLE;
}
switch (flag)
{
case 0: // %f
FloatResolve(&value, F_MAXPRECISION, rec->precision, fRec);
break;
case 1: // %e or %E
FloatResolve(&value, rec->precision + 1, 9999, fRec);
break;
case 2: // %g or %G
FloatResolve(&value, rec->precision, 9999, fRec);
}
if (fRec->negative)
rec->negative = -1;
}
// 格式化小数字串。参数:缓冲区,格式记录,数字串,数字串长度。返回缓冲区尾偏移
static LPSTR FormatDecimalA(LPSTR buffer, FormatRec *rec, LPCSTR str, INT strLen)
{
LPSTR p = buffer;
INT spaces = rec->width - strLen;
if (rec->negative)
{
spaces --;
if (rec->left || rec->zero)
*p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS);
}
if (rec->left == FALSE)
{
if (spaces > 0)
{
memset(p, rec->zero? CHAR_ZERO : CHAR_SPACE, spaces);
p += spaces;
}
if (rec->negative && !rec->zero)
*p ++ = (rec->negative == -1? CHAR_NEG : CHAR_POS);
}
memcpy(p, str, strLen);
p += strLen;
if (rec->left && spaces > 0)
{
memset(p, CHAR_SPACE, spaces);
p += spaces;
}
return p;
}
#define F_MAXEXPONENT 45
#define F_MINEXPONENT -45
// 输出指数字符串到buffer,返回指数字符串长度
INT PutExponent(LPSTR buffer, CONST FloatRec *rec)
{
LPSTR p = buffer;
INT e, exp = rec->digits[0]? rec->exponent - 1 : 0;
*p ++ = rec->negative & 0x80? 'E' : 'e';
if (exp < 0)
{
exp = -exp;
*p ++ = '-';
}
else *p ++ = '+';
if ((e = (exp / 1000)) != 0)
{
*p ++ = e + 0x30;
exp %= 1000;
}
*p ++ = exp / 100 + 0x30;
exp %= 100;
*(PUSHORT)p = (((exp % 10) << 8) | (exp / 10)) + 0x3030;
return (INT)(p - buffer + 2);
}
// 按浮点数记录信息转换为指数格式数字串。
// 参数:缓冲区,浮点数记录,转换精度,是否强制小数位
static INT FloatExponentA(LPSTR buffer, CONST FloatRec *rec, INT precision, BOOL decPoint)
{
LPSTR p = buffer;
LPCSTR digits = rec->digits;
if (*digits)
*p ++ = *digits ++;
else
*p ++ = '0';
if (precision > 0 || decPoint)
{
for (*p ++ = '.'; precision > 0 && *digits; *p ++ = *digits ++, precision --);
for (; precision > 0; *p ++ = '0', precision --);
}
p += PutExponent(p, rec);
return (INT)(p - buffer);
}
// 按浮点数记录信息转换为小数格式数字串。
// 参数:缓冲区,浮点数记录,转换精度,是否强制小数位
static INT FloatDecimalA(LPSTR buffer, CONST FloatRec *rec, INT precision, BOOL decPoint)
{
LPSTR p;
LPCSTR digits;
INT exp = rec->exponent;
if (exp > F_MAXEXPONENT || exp < F_MINEXPONENT)
return FloatExponentA(buffer, rec, precision, decPoint);
p = buffer;
digits = rec->digits;
if (exp > 0)
{
for (; exp > 0 && *digits; *p ++ = *digits ++, exp --);
for (; exp > 0; *p ++ = '0', exp --);
if (decPoint || precision > 0)
*p ++ = '.';
}
else
{
exp = -exp;
precision -= exp;
if (precision < 0)
{
exp += precision;
precision = 0;
}
*p ++ = '0';
if (exp > 0 || decPoint || precision > 0)
{
*p ++ = '.';
for (; exp > 0; *p ++ = '0', exp --);
}
}
for (; precision > 0 && *digits; *p ++ = *digits ++, precision --);
for (; precision > 0; *p ++ = '0', precision --);
return (INT)(p - buffer);
}
// 浮点数格式化为小数串。参数:缓冲区,格式记录。返回缓冲区尾偏移
static LPSTR FormatFloatFA(LPSTR buffer, FormatRec *rec)
{
FloatRec fRec;
INT len;
CHAR tmp[F_MAXDECIMALS+48];
GetFloatRec(rec, 0, &fRec);
if (fRec.digits[0] > '9') // nan or inf
return FormatDecimalA(buffer, rec, fRec.digits, 3);
len = FloatDecimalA(tmp, &fRec, rec->precision, rec->decimals);
return FormatDecimalA(buffer, rec, tmp, len);
}
// 浮点数格式化为指数串。参数:缓冲区,格式记录。返回缓冲区尾偏移
static LPSTR FormatFloatEA(LPSTR buffer, FormatRec *rec, CHAR expChar)
{
FloatRec fRec;
INT len;
CHAR tmp[F_MAXDECIMALS+8];
GetFloatRec(rec, 1, &fRec);
if (fRec.digits[0] > '9') // nan or inf
return FormatDecimalA(buffer, rec, fRec.digits, 3);
if (expChar == 'E')
fRec.negative |= 0x80; // 高位置1,大写
len = FloatExponentA(tmp, &fRec, rec->precision, rec->decimals);
return FormatDecimalA(buffer, rec, tmp, len);
}
// 浮点数格式化为小数串或者指数串。参数:缓冲区,格式记录。返回缓冲区尾偏移
static LPSTR FormatFloatGA(LPSTR buffer, FormatRec *rec, CHAR expChar)
{
FloatRec fRec;
INT len, precision;
CHAR tmp[F_MAXDECIMALS+48];
GetFloatRec(rec, 2, &fRec);
if (fRec.digits[0] > '9') // nan or inf
return FormatDecimalA(buffer, rec, fRec.digits, 3);
if (expChar == 'G')
fRec.negative |= 0x80; // 高位置1,大写
if (fRec.exponent > rec->precision || fRec.exponent < -3)
{
precision = rec->decimals? rec->precision - 1 : lstrlenA(fRec.digits) - 1;
len = FloatExponentA(tmp, &fRec, precision, rec->decimals);
}
else
{
precision = rec->decimals? rec->precision - fRec.exponent : lstrlenA(fRec.digits) - fRec.exponent;
if (precision < 0) precision = 0;
len = FloatDecimalA(tmp, &fRec, precision, rec->decimals);
}
return FormatDecimalA(buffer, rec, tmp, len);
}
在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