用整数理解printf()

时间:2012-12-14 11:54:05

标签: c integer printf integer-arithmetic

我有一个关于printf()方法如何打印整数,有符号或无符号的问题。有一天,我发现自己在考虑将二进制序列转换为人类可以理解的十进制数字序列是多么困难,因为计算机没有小数概念。

下面,我有一个printf()方法(来自here)及其相关方法。我已尽力了解printi()如何运作,正如您在评论中所看到的那样:

#define PAD_RIGHT 1
#define PAD_ZERO 2

#include <stdarg.h>

static void printchar(char **str, int c)
{
    extern int putchar(int c);

    if (str) {
        **str = c;
        ++(*str);
    }
    else (void)putchar(c);
}

static int prints(char **out, const char *string, int width, int pad)
{
    register int pc = 0, padchar = ' ';

    if (width > 0) {
        register int len = 0;
        register const char *ptr;
        for (ptr = string; *ptr; ++ptr) ++len;
        if (len >= width) width = 0;
        else width -= len;
        if (pad & PAD_ZERO) padchar = '0';
    }
    if (!(pad & PAD_RIGHT)) {
        for ( ; width > 0; --width) {
            printchar (out, padchar);
            ++pc;
        }
    }
    for ( ; *string ; ++string) {
        printchar (out, *string);
        ++pc;
    }
    for ( ; width > 0; --width) {
        printchar (out, padchar);
        ++pc;
    }

    return pc;
}

/* the following should be enough for 32 bit int */
#define PRINT_BUF_LEN 12

static int printi(char **out, int i, int b, int sg, int width, int pad, int letbase)
{
    /*
        i is the number we are turning into a string
        b is the base, i.e. base 10 for decimal
        sg is if the number is signed, i.e. 1 for signed (%d), 0 for unsigned (%u)

        By default, width and pad are 0, letbase is 97
    */

    char print_buf[PRINT_BUF_LEN];
    register char *s;
    register int t, neg = 0, pc = 0;
    register unsigned int u = i;

    if (i == 0)
    {
        print_buf[0] = '0';
        print_buf[1] = '\0';
        return prints(out, print_buf, width, pad);
    }

    if (sg && b == 10 && i < 0)
    {
        neg = 1;
        u = -i;
    }

    s = print_buf + PRINT_BUF_LEN - 1;
    *s = '\0';

    while (u)
    {
        t = u % b;

        if (t >= 10)
            t += letbase - '0' - 10;

        *--s = t + '0';
        u /= b;
    }

    if (neg)
    {
        if (width && (pad & PAD_ZERO))
        {
            printchar(out, '-');
            ++pc;
            --width;
        }
        else
            *--s = '-';
    }

    return pc + prints(out, s, width, pad);
}

static int print(char** out, const char* format, va_list args)
{
    register int width, pad;
    register int pc = 0;
    char scr[2];

    for (; *format != 0; ++format)
    {
        if (*format == '%')
        {
            ++format;
            width = pad = 0;

            if (*format == '\0')
                break;

            if (*format == '%')
                goto out;

            if (*format == '-')
            {
                ++format;
                pad = PAD_RIGHT;
            }

            while (*format == '0')
            {
                ++format;
                pad |= PAD_ZERO;
            }

            for (; *format >= '0' && *format <= '9'; ++format)
            {
                width *= 10;
                width += *format - '0';
            }

            if (*format == 's')
            {
                register char* s = (char*) va_arg(args, int);
                pc += prints(out, s ? s : "(null)", width, pad);
                continue;
            }

            if (*format == 'd')
            {
                pc += printi(out, va_arg(args, int), 10, 1, width, pad, 'a');
                continue;
            }

            if (*format == 'x')
            {
                pc += printi(out, va_arg(args, int), 16, 0, width, pad, 'a');
                continue;
            }

            if (*format == 'X')
            {
                pc += printi(out, va_arg(args, int), 16, 0, width, pad, 'A');
                continue;
            }

            if (*format == 'u')
            {
                pc += printi(out, va_arg(args, int), 10, 0, width, pad, 'a');
                continue;
            }

            if (*format == 'c')
            {
                /* char are converted to int then pushed on the stack */
                scr[0] = (char) va_arg(args, int);
                scr[1] = '\0';
                pc += prints(out, scr, width, pad);
                continue;
            }
        }
        else
        {
            out:
            printchar (out, *format);
            ++pc;
        }
    }

    if (out)
        **out = '\0';

    va_end(args);

    return pc;
}

int printf(const char *format, ...)
{
        va_list args;

        va_start( args, format );
        return print( 0, format, args );
}

如果有一件事我讨厌阅读库源代码,那就是它几乎无法读取。带有一个字符的变量名称和没有注释来解释它们是一种痛苦。

请您以简单的方式解释一下将整数转换为十进制数字串的确切方法吗?

3 个答案:

答案 0 :(得分:2)

您粘贴的代码不难阅读。我怀疑你可能早早放弃了。

暂时忽略负数的可能性,这个printi()例程:

  • 创建一个缓冲区以将数字打印成12个字符宽
  • 将字符指针s设置为指向该缓冲区的 end ** NULL - 终止它,然后将指针移动一个字符到“左”

然后例程进入循环,只要数字保持&gt; 0

  • MOD by 10(即除以10并取余数)
    • 这成为s指向的数字,因此ASCII表示放在那里
    • s再次向左移动
  • 将数字设置为自身/ 10;这将删除刚刚打印的数字
  • 只要有更多数字要打印,
  • 重复循环

这里唯一棘手的问题是处理负数,但如果你了解负数是如何存储的,那根本就不是很棘手。

答案 1 :(得分:1)

也许我一直在盯着模板库标题太长时间,但那个库代码对我来说看起来很可读!

我将解释主循环,因为其余的(杂乱的标志等)应该很容易弄明白。

while (u)
{
    t = u % b;

    if (t >= 10)
        t += letbase - '0' - 10;

    *--s = t + '0';
    u /= b;
}

基本上我们正在做的是一次一个地提取数字,从右到左。假设b == 10(即%d%u的通常情况)。 %运算符(称为模运算符)计算整数除法后剩余的余数。第一次行t = u % b;运行时,它会计算输出字符串的最右边数字 - 将数字u除以10后剩下的数字为余数。(假设数字为u }是493:除以10之后的余数是3,最右边的数字。)

将最右边的数字提取到t后,if语句决定如果该数字为10或更大,则“呼叫”该数字。此修正相当于调整t,以便当'0'(数字'0'的ASCII value,即48)添加到下一行时,结果将是一个从'开始的字母' a'或'A'(产生十六进制数字和大于10的碱基的其他数字)。

之后的行将数字写入缓冲区。它进入print_buf缓冲区的最右边的字符(注意s之前的初始化是如何指向此缓冲区的 end ,而不是通常情况下开始)。随后指针s向左移动一个字符以准备下一个字符。

以下行u /= b简单地将u除以10,有效地丢弃最右边的数字。 (这是有效的,因为整数除法从不产生分数,并且总是向下舍入。)然后打开第二个最右边的数字,用于下一个要处理的循环迭代。冲洗,重复。当没有任何东西时,循环最终停止(条件while (u)等同于条件while (u != 0))。

答案 2 :(得分:0)

将正整数I转换为基数10的方法基本上是:

if (i == 0)
    printf("0");
else while (i != 0) {
    unsigned int j = i / 10;
    unsigned int digit = i - 10 * j;
    printf("%c", digit + '0');
    i = j;
}

除此之外,它会向后打印出数字。