在C语言中实现printf函数。

3

这可能是个愚蠢的问题,但是......我尝试实现printf,但是出现了一些输出结果不符合我的预期的原因。你有什么想法吗?我会很感激你的帮助。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h> 

static int print(const char *restrict fmt, ...);
static int getfloat(float *);
static char *itoa(int, char *, int);
static void _strrev(char *);


int
main(void)
{
    float i1 = 0.0, i2 = 0.0, noi1 = 0.0, noi2 = 0.0, res = 0.0;

    print("weight - item 1: ");
    getfloat(&i1);

    print("no. of item 1: ");
    getfloat(&noi1);

    print("weight - item 2: ");
    getfloat(&i2);

    print("no. of item 2: ");
    getfloat(&noi2);

    res = ((i1 * noi1) + (i2 * noi2)) / (noi1 + noi2);
    print("%f\n", res);

    exit(EXIT_SUCCESS);
}


static int
print(const char *restrict fmt, ...)
{
    va_list ap;
    char buf[BUFSIZ] = {0}, tmp[20] = {0};
    char *str_arg;
    int i = 0, j = 0;

    va_start(ap, fmt);

    while (fmt[i] != '\0') {

        if (fmt[i] == '%') {
            i++;
            switch (fmt[i]) {
                case 'c':
                    buf[j] = (char)va_arg(ap, int);
                    j++;
                    break;

                case 'd':
                    itoa(va_arg(ap, int), tmp, 10);
                    strcpy(&buf[j], tmp);
                    j += strlen(tmp);
                    break;

                case 'f':
                    gcvt(va_arg(ap, double), 10, tmp);
                    strcpy(&buf[j], tmp);
                    j += strlen(tmp);
                    break;

                case 's':
                    str_arg = va_arg(ap, char *);
                    strcpy(&buf[j], str_arg);
                    j += strlen(str_arg);
                    break;

                default:
                    break;
            }
        } else { buf[j++ ] = fmt[i]; } 
        ++i;
    }
    fwrite(buf, j, 1, stdout);
    va_end(ap);
    return (j);
}


static int
getfloat(float *p)
{
    int c, sign = 0;
    float pwr = 0.0;


    while (c = getc(stdin), c == ' ' || c == '\t' || c == '\n')
        ;   /* ignore white spaces */
    
    sign = 1;   /* record sign */
    if (c == '+' || c == '-') {
        sign = (c == '+') ? 1 : -1;
        c = getc(stdin);
    }


    for (*p = 0.0; isdigit(c); c = getc(stdin))
        *p = 10.0 * *p + c - '0';

    if (c == '.') { c = getc(stdin); }

    for (pwr = 1.0; isdigit(c); c = getc(stdin)) {
        *p = 10.0 * *p + c - '0';
        pwr *= 10.0;
    }
    
    *p *= sign / pwr;
    if (c != EOF)
        ungetc(c, stdout);
    return (float)c;
}


static char *
itoa(int n, char *strout, int base)
{
    int i, sign;

    if ((sign = n) < 0)
        n -= n;

    i = 0;
    do {
        strout[i++] = n % base + '0';
    } while ((n /= base) != 0);

    if (sign < 0) { strout[i++] = '-'; }
    strout[i] = '\0';

    _strrev(strout);
    return (strout);
}


static void
_strrev(char *str)
{
    int i = 0, j = strlen(str) - 1;

    for ( ; i < j; ++i, --j) {
        int tmp = str[i];
        str[i] = str[j];
        str[j] = tmp;
    }
}

这是我获得的输出结果:

19.44444466

而我期望的输出结果如下:(或者说,至少我希望获得以下输出结果,当我使用printf时,我就能得到这样的输出结果)

19.444445 

2
“19.444445”是“19.44444466”四舍五入后的结果。如果您要求gcvt()返回8位数字,您会得到什么结果?反之,如果您使用printf("%.8f\n", res);,您会得到什么结果? - Weather Vane
1
@WeatherVane 当我使用 printf("%.8f\n", res); 时,得到的结果是 19.44444466,现在我想我更或多或少地理解了。谢谢! - jr.çhåvez__07
2
tmp[20] 对于 gcvt(-DBL_MAX, 10, tmp) 来说太小了。更像是 tmp[320]。即使是 float 类型,也可能需要大约 50 个字符。 - chux - Reinstate Monica
2个回答

3
   f, F   The double argument is rounded and converted to decimal
          notation in the style [-]ddd.ddd, where the number of
          digits after the decimal-point character is equal to the
          precision specification.  If the precision is missing, it
          is taken as 6;

https://man7.org/linux/man-pages/man3/printf.3.html

%f 的默认精度是六位,因此 printf() 会将结果舍入到六位小数。

你需要调整 gcvt() 函数的参数 ndigit,该参数表示有效数字的总位数(包括小数点前和小数点后)。在这个特定的数字中,你传递了参数 10,因此答案有两位小数点前和八位小数点后。


1
似乎gcvt()不能像printf()那样精确地工作,至少在您的编译器上是这样。请使用相同值的“真正的”printf来检查它。
由于您没有提供用于测试的数字(避免使用getfloat()并直接使用所需常量初始化i1i2noi1noi2),我无法运行它并告诉您为什么 - 或者它是否会在我的编译器中发生。
通常,printf的源代码至少比您的源代码大两倍,因此您可能错过了一些恶毒的子情况。如果我没记错的话,printf有解码IEEE-754的代码,而不依赖于gcvt

1
我用于测试的数字是, 重量 - 文章1:15 | 文章1数量:5 | 项目2重量:25 | 文章2数量:4我想我还需要更深入地了解gcvt(),非常感谢。 - jr.çhåvez__07

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接