Как отформатировать число от 1123456789 до 1,123,456,789 в C? - PullRequest
66 голосов
/ 20 сентября 2009

Как я могу на языке C отформатировать число от 1123456789 до 1,123,456,789? Я пытался использовать printf("%'10d\n", 1123456789);, но это не работает.

Не могли бы вы что-нибудь посоветовать? Чем проще решение, тем лучше.

Ответы [ 19 ]

69 голосов
/ 28 июля 2012

Если ваш printf поддерживает флаг ' (как того требует POSIX 2008 printf()), вы, вероятно, можете сделать это, просто установив соответствующий язык. Пример:

#include <stdio.h>
#include <locale.h>

int main(void)
{
    setlocale(LC_NUMERIC, "");
    printf("%'d\n", 1123456789);
    return 0;
}

И построить и запустить:

$ ./example 
1,123,456,789

Протестировано на Mac OS X и Linux (Ubuntu 10.10).

40 голосов
/ 20 сентября 2009

Вы можете сделать это рекурсивно следующим образом (остерегайтесь INT_MIN, если вы используете дополнение к двум, вам понадобится дополнительный код для управления этим):

void printfcomma2 (int n) {
    if (n < 1000) {
        printf ("%d", n);
        return;
    }
    printfcomma2 (n/1000);
    printf (",%03d", n%1000);
}

void printfcomma (int n) {
    if (n < 0) {
        printf ("-");
        n = -n;
    }
    printfcomma2 (n);
}

Сумма:

  • Пользователь вызывает printfcomma с целым числом, особый случай отрицательных чисел обрабатывается простой печатью «-» и превращением числа в положительное (этот бит не будет работать с INT_MIN).
  • Когда вы введете printfcomma2, число меньше 1000 просто напечатает и вернет.
  • В противном случае рекурсия будет вызываться на следующем уровне вверх (таким образом, 1,234,567 будет вызываться с 1,234, затем с 1) до тех пор, пока не будет найдено число меньше 1000.
  • Тогда это число будет напечатано, и мы вернемся вверх по дереву рекурсии, напечатав запятую и следующий номер по ходу.

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

#include <stdio.h>

void printfcomma (int n) {
    if (n < 0) {
        printf ("-");
        printfcomma (-n);
        return;
    }
    if (n < 1000) {
        printf ("%d", n);
        return;
    }
    printfcomma (n/1000);
    printf (",%03d", n%1000);
}

int main (void) {
    int x[] = {-1234567890, -123456, -12345, -1000, -999, -1,
               0, 1, 999, 1000, 12345, 123456, 1234567890};
    int *px = x;
    while (px != &(x[sizeof(x)/sizeof(*x)])) {
        printf ("%-15d: ", *px);
        printfcomma (*px);
        printf ("\n");
        px++;
    }
    return 0;
}

и вывод:

-1234567890    : -1,234,567,890
-123456        : -123,456
-12345         : -12,345
-1000          : -1,000
-999           : -999
-1             : -1
0              : 0
1              : 1
999            : 999
1000           : 1,000
12345          : 12,345
123456         : 123,456
1234567890     : 1,234,567,890

Итеративное решение для тех, кто не доверяет рекурсии (хотя единственной проблемой рекурсии, как правило, является пространство стека, которое здесь не будет проблемой, так как оно будет всего на несколько уровней глубиной даже для 64-битного целого числа ):

void printfcomma (int n) {
    int n2 = 0;
    int scale = 1;
    if (n < 0) {
        printf ("-");
        n = -n;
    }
    while (n >= 1000) {
        n2 = n2 + scale * (n % 1000);
        n /= 1000;
        scale *= 1000;
    }
    printf ("%d", n);
    while (scale != 1) {
        scale /= 1000;
        n = n2 / scale;
        n2 = n2  % scale;
        printf (",%03d", n);
    }
}

Оба из них генерируют 2,147,483,647 для INT_MAX.

11 голосов
/ 20 сентября 2009

Вот очень простая реализация. Эта функция содержит проверку ошибок no , размеры буфера должны проверяться вызывающей стороной. Это также не работает для отрицательных чисел. Такие улучшения оставлены читателю в качестве упражнения.

void format_commas(int n, char *out)
{
    int c;
    char buf[20];
    char *p;

    sprintf(buf, "%d", n);
    c = 2 - strlen(buf) % 3;
    for (p = buf; *p != 0; p++) {
       *out++ = *p;
       if (c == 1) {
           *out++ = ',';
       }
       c = (c + 1) % 3;
    }
    *--out = 0;
}
6 голосов
/ 17 сентября 2011

Egads! Я делаю это все время, используя gcc / g ++ и glibc в Linux, и да, оператор 'может быть нестандартным, но мне нравится его простота.

#include <stdio.h>
#include <locale.h>

int main()
{
    int bignum=12345678;

    setlocale(LC_ALL,"");

    printf("Big number: %'d\n",bignum);

    return 0;
}

Дает вывод:

Большое число: 12,345,678

Просто запомните вызов 'setlocale', иначе он ничего не отформатирует.

4 голосов
/ 18 марта 2011

Возможно, будет интересна версия с поддержкой локали.

#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <limits.h>

static int next_group(char const **grouping) {
    if ((*grouping)[1] == CHAR_MAX)
        return 0;
    if ((*grouping)[1] != '\0')
        ++*grouping;
    return **grouping;
}

size_t commafmt(char   *buf,            /* Buffer for formatted string  */
                int     bufsize,        /* Size of buffer               */
                long    N)              /* Number to convert            */
{
    int i;
    int len = 1;
    int posn = 1;
    int sign = 1;
    char *ptr = buf + bufsize - 1;

    struct lconv *fmt_info = localeconv();
    char const *tsep = fmt_info->thousands_sep;
    char const *group = fmt_info->grouping;
    char const *neg = fmt_info->negative_sign;
    size_t sep_len = strlen(tsep);
    size_t group_len = strlen(group);
    size_t neg_len = strlen(neg);
    int places = (int)*group;

    if (bufsize < 2)
    {
ABORT:
        *buf = '\0';
        return 0;
    }

    *ptr-- = '\0';
    --bufsize;
    if (N < 0L)
    {
        sign = -1;
        N = -N;
    }

    for ( ; len <= bufsize; ++len, ++posn)
    {
        *ptr-- = (char)((N % 10L) + '0');
        if (0L == (N /= 10L))
            break;
        if (places && (0 == (posn % places)))
        {
            places = next_group(&group);
            for (int i=sep_len; i>0; i--) {
                *ptr-- = tsep[i-1];
                if (++len >= bufsize)
                    goto ABORT;
            }
        }
        if (len >= bufsize)
            goto ABORT;
    }

    if (sign < 0)
    {
        if (len >= bufsize)
            goto ABORT;
        for (int i=neg_len; i>0; i--) {
            *ptr-- = neg[i-1];
            if (++len >= bufsize)
                goto ABORT;
        }
    }

    memmove(buf, ++ptr, len + 1);
    return (size_t)len;
}

#ifdef TEST
#include <stdio.h>

#define elements(x) (sizeof(x)/sizeof(x[0]))

void show(long i) {
    char buffer[32];

    commafmt(buffer, sizeof(buffer), i);
    printf("%s\n", buffer);
    commafmt(buffer, sizeof(buffer), -i);
    printf("%s\n", buffer);
}


int main() {

    long inputs[] = {1, 12, 123, 1234, 12345, 123456, 1234567, 12345678 };

    for (int i=0; i<elements(inputs); i++) {
        setlocale(LC_ALL, "");
        show(inputs[i]);
    }
    return 0;
}

#endif

В этом есть ошибка (но я бы посчитал ее незначительной). На оборудовании, дополняющем два, оно не будет правильно преобразовывать наиболее отрицательное число, потому что оно пытается преобразовать отрицательное число в эквивалентное положительное число с помощью N = -N; В дополнении к двум, максимально отрицательное число не имеет соответствующего положительного числа. , если вы не продвинете это к большему типу. Одним из способов обойти это является продвижение числа соответствующего типа без знака (но это несколько нетривиально).

3 голосов
/ 20 сентября 2009

Без рекурсии или обработки строк, математический подход:

#include <stdio.h>
#include <math.h>

void print_number( int n )
{
    int order_of_magnitude = (n == 0) ? 1 : (int)pow( 10, ((int)floor(log10(abs(n))) / 3) * 3 ) ;

    printf( "%d", n / order_of_magnitude ) ;

    for( n = abs( n ) % order_of_magnitude, order_of_magnitude /= 1000;
        order_of_magnitude > 0;
        n %= order_of_magnitude, order_of_magnitude /= 1000 )
    {
        printf( ",%03d", abs(n / order_of_magnitude) ) ;
    }
}

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

Обратите внимание, что фактический символ, используемый для разделения тысяч, зависит от конкретной локали.

Редактировать : См. Комментарии @ Chux ниже для улучшения.

3 голосов
/ 17 июля 2014

На основе @Greg Hewgill's, но учитывает отрицательные числа и возвращает размер строки.

size_t str_format_int_grouped(char dst[16], int num)
{
    char src[16];
    char *p_src = src;
    char *p_dst = dst;

    const char separator = ',';
    int num_len, commas;

    num_len = sprintf(src, "%d", num);

    if (*p_src == '-') {
        *p_dst++ = *p_src++;
        num_len--;
    }

    for (commas = 2 - num_len % 3;
         *p_src;
         commas = (commas + 1) % 3)
    {
        *p_dst++ = *p_src++;
        if (commas == 1) {
            *p_dst++ = separator;
        }
    }
    *--p_dst = '\0';

    return (size_t)(p_dst - dst);
}
1 голос
/ 20 сентября 2009

Еще одна итерационная функция

int p(int n) {
  if(n < 0) {
    printf("-");
    n = -n;
  }

  int a[sizeof(int) * CHAR_BIT / 3] = { 0 };
  int *pa = a;
  while(n > 0) {
    *++pa = n % 1000;
    n /= 1000;
  }
  printf("%d", *pa);
  while(pa > a + 1) {
    printf(",%03d", *--pa);
  }
}
1 голос
/ 10 сентября 2013

другое решение, сохраняя результат в массиве int, максимальный размер, если 7 из-за длинного long int может обрабатывать числа в диапазоне от 9,223,372,036,854,775,807 до -9,223,372,036,854,775,807 note it is not an unsigned

функция нерекурсивной печати

static void printNumber (int numbers[8], int loc, int negative)
{
    if (negative)
    {
        printf("-");
    }
    if (numbers[1]==-1)//one number
    {
        printf("%d ", numbers[0]);
    }
    else
    {
        printf("%d,", numbers[loc]);
        while(loc--)
        {
            if(loc==0)
            {// last number
                printf("%03d ", numbers[loc]);
                break;
            }
            else
            { // number in between
                printf("%03d,", numbers[loc]);
            }
        }
    }
}

вызов основной функции

static void getNumWcommas (long long int n, int numbers[8])
{
    int i;
    int negative=0;
    if (n < 0)
    {
        negative = 1;
        n = -n;
    }
    for(i = 0; i<7; i++)
    {
        if (n < 1000)
        {
            numbers[i] = n;
            numbers[i+1] = -1;
            break;
        }
        numbers[i] = n%1000;
        n/=1000;
    }

    printNumber(numbers, i, negative);// non recursive print
}

тестовый вывод

-9223372036854775807: -9,223,372,036,854,775,807
-1234567890         : -1,234,567,890
-123456             : -123,456
-12345              : -12,345
-1000               : -1,000
-999                : -999
-1                  : -1
0                   : 0
1                   : 1
999                 : 999
1000                : 1,000
12345               : 12,345
123456              : 123,456
1234567890          : 1,234,567,890
9223372036854775807 : 9,223,372,036,854,775,807

в основном () классе

int numberSeperated[8];
long long int number = 1234567890LL;
getNumWcommas(number, numberSeperated );

если все, что нужно для печати, переместите int numberSeperated[8]; в функцию getNumWcommas и назовите ее так getNumWcommas(number);

1 голос
/ 23 августа 2015

Мой ответ не форматирует результат точно так, как показано на иллюстрации вопроса, но может удовлетворить фактическую потребность в некоторых случаях с простым однострочником или макросом. Можно расширить его, чтобы генерировать больше тысяч групп по мере необходимости.

Результат будет выглядеть, например, следующим образом:

Value: 0'000'012'345

код:

printf("Value: %llu'%03lu'%03lu'%03lu\n", (value / 1000 / 1000 / 1000), (value / 1000 / 1000) % 1000, (value / 1000) % 1000, value % 1000);
...