Понимание printf() с целыми числами

У меня есть вопрос о том, как метод printf() печатает целые числа, со знаком или без знака. Однажды я поймал себя на мысли о том, как сложно должно быть преобразовать двоичную последовательность в последовательность десятичных цифр, которую может понять человек, учитывая, что компьютер не имеет представления о десятичном числе.

Ниже у меня есть метод printf() (из здесь) со связанными с ним методами . Я пытался понять как можно больше о том, как работает 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 );
}

Если есть что-то, что я ненавижу в чтении исходного кода библиотеки, так это то, что его почти невозможно прочитать. Имена переменных с одним символом и без комментария для их объяснения - это боль.

Не могли бы вы объяснить простым языком, что именно делает метод для преобразования целого числа в строку десятичных цифр?


person Doddy    schedule 14.12.2012    source источник
comment
много вопросов в одном посте, наверное лучше разбить на несколько постов   -  person AndersK    schedule 14.12.2012
comment
Готово (stackoverflow.com/questions/13878485/).   -  person Doddy    schedule 14.12.2012


Ответы (3)


Код, который вы вставили, нетрудно прочитать. Я подозреваю, что вы рано сдались.

Игнорируя на мгновение возможность появления отрицательного числа, эта процедура printi():

  • создает буфер для печати числа, шириной 12 символов
  • устанавливает указатель символа s так, чтобы он указывал на конец этого буфера ** NULL-завершает его, затем перемещает указатель на один символ "влево"

Затем процедура входит в цикл, пока число остается> 0

  • MOD на 10 (то есть разделить на 10 и взять остаток)
    • this becomes the digit that s is pointing to, so the ASCII representation is put there
    • s is moved to the left again
  • установить число на себя / 10; это удаляет только что напечатанную цифру
  • повторяйте цикл до тех пор, пока есть больше цифр для печати

Единственная сложная вещь здесь — это работа с отрицательными числами, но если вы понимаете, как хранятся отрицательные числа, это совсем не сложно.

person mah    schedule 14.12.2012

Возможно, я слишком долго смотрел на заголовки библиотеки шаблонов, но код этой библиотеки выглядит довольно читабельным для меня!

Я объясню основной цикл, так как остальное (манипулирование знаком и т. д.) должно быть довольно легко понять.

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' (значение ASCII цифры '0', что равно 48) добавляется в следующей строке, результатом будет буква, начинающаяся с «а» или «А» (для получения шестнадцатеричных цифр и других цифр для оснований больше 10).

Строка после этого записывает цифру в буфер. Он входит в самый правый символ буфера print_buf (обратите внимание, что s ранее инициализировался, чтобы указать на конец этого буфера, а не на его начало, как это обычно бывает). Указатель s впоследствии перемещается на один символ влево для подготовки к следующему символу.

Следующая строка, u /= b, просто делит u на 10, эффективно отбрасывая самую правую цифру. (Это работает, потому что целочисленное деление никогда не дает дробей и всегда округляется в меньшую сторону.) Затем открывается вторая крайняя правая цифра для обработки следующей итерации цикла. Смыть, повторить. Цикл окончательно останавливается, когда ничего не остается (условие while (u) эквивалентно условию while (u != 0)).

person j_random_hacker    schedule 14.12.2012

Метод для преобразования положительного целого числа 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;
}

За исключением того, что это печатает число в обратном порядке.

person kmkaplan    schedule 14.12.2012
comment
И за исключением того, что оператор <> не существует в C... В основном этот ответ бесполезен. - person ; 14.12.2012
comment
@H2CO3 исправил оператор !=. - person kmkaplan; 14.12.2012