Formatting numeric values with commas

Luther von Wulf 1 Tallied Votes 470 Views Share

This function reformats a numeric sequence to include grouping in the usual human readable format. "123456789" becomes "123,456,789", for example. The formatting is localized to the current running machine. In a German locale the previous example becomes "123.456.789". In a Hindi locale it becomes "12,34,56,789". Printing numbers for human consumption is a common request. C does not offer direct support for it, nor are there many examples that are complete for use outside of the US that I could find.

Adak commented: Looks very robust - well done. +3
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <locale.h>
#include <ctype.h>

typedef enum { FALSE, TRUE } BOOL;

struct locale {
    struct lconv* settings;
    char* previous;
};

static struct locale change_locale(int category, char const* name) {
    struct locale loc;
    loc.previous = setlocale(category, NULL);
    setlocale(category, name);
    loc.settings = localeconv();
    return loc;
}

BOOL format_numeric(char const* num, char* buf, int buf_sz) {
    struct locale loc = change_locale(LC_NUMERIC, "");
    int group_sz = 0, sep_sz = strlen(loc.settings->thousands_sep);
    char* group = loc.settings->grouping;
    int x = strlen(num)-1, y = 0;
    while (x >= 0) { // copy num backwards into buf
        if (!isdigit(num[x]) || y >= buf_sz-1)
            return FALSE;
        if (*group == CHAR_MAX || group_sz++ != *group) {
            // verbatim copy, no grouping or still within a group
            buf[y++] = num[x--];
        }
        else {
            // terminate group with group separator
            memcpy(buf + y, loc.settings->thousands_sep, sep_sz);
            if (group != loc.settings->grouping && *(group+1) != 0)
                ++group;
            group_sz = 0;
            y += sep_sz;
        }
    }
    buf[y] = '\0';
    // the string was built backwards. reverse it here
    for (x = 0; x < --y; ++x) {
        char tmp = buf[x];
        buf[x] = buf[y];
        buf[y] = tmp;
    }
    setlocale(LC_NUMERIC, loc.previous); // restore original locale
    return TRUE;
}

void main() {
    char input[BUFSIZ], output[BUFSIZ];
    do {
        printf("enter a number> ");
        if (fgets(input, BUFSIZ, stdin)) {
            int len = strlen(input);
            if (len > 0 && input[len-1] == '\n')
                input[len-1] = '\0';
            if (format_numeric(input, output, BUFSIZ))
                printf("|%s|\n", output);
            else
                fprintf(stderr, "conversion error for '%s'\n", input);
        }
    } while (!feof(stdin));
}