Wie formatiere ich eine Zahl mit Komma als Tausendertrennzeichen in C?

Lesezeit: 10 Minuten

Benutzeravatar von goe
geh

Wie kann ich in C eine große Zahl aus zB formatieren? 1123456789 zu 1,123,456,789?

Ich habe versucht, mit printf("%'10d\n", 1123456789)aber das geht nicht.

Können Sie etwas raten? Je einfacher die Lösung, desto besser.

  • Nur ein FYI: das ‘Tausendertrennzeichen’-Flag für die printf() Familie formatierter IO-Funktionen (das einfache Anführungszeichen: ‘) ist ein nicht standardmäßiges Flag, das nur in einigen wenigen Bibliotheksimplementierungen unterstützt wird. Schade das es nicht Standard ist.

    – Michael Burr

    20. September 2009 um 0:55 Uhr

  • Es ist vom Gebietsschema abhängig. Laut dem Linux-Manpagesieht es aus LC_NUMERIC. Ich weiß jedoch nicht, welches Gebietsschema dies unterstützt.

    – Joey Adams

    4. November 2010 um 4:13 Uhr


  • @Joey, setze die LC_NUMERIC Gebietsschema zum Strom "" macht das ' funktioniert auf meinem Mac und auf einem Linux-Rechner, den ich gerade überprüft habe.

    – Karl Norum

    27. Juli 2012 um 20:32 Uhr


  • Beachten Sie, dass die POSIX 2008 (2013)-Versionen der printf() Funktionsfamilie standardisiert die Verwendung der ' (einfaches Anführungszeichen oder Apostroph) mit den Konvertierungsspezifikationen für die Dezimalzahlenformatierung, um anzugeben, dass die Zahl mit Tausendertrennzeichen formatiert werden soll.

    – Jonathan Leffler

    26. April 2015 um 18:09 Uhr

  • Beachten Sie dies auch in der Voreinstellung "C" Gebietsschema, das nicht-monetäre Tausendertrennzeichen ist undefiniert, also die "%'d" erzeugt keine Kommas in der "C" Gebietsschema. Sie müssen ein Gebietsschema mit einem geeigneten nicht-monetären Tausendertrennzeichen festlegen. Häufig, setlocale(LC_ALL, ""); wird die Aufgabe erledigen – andere Werte für den Gebietsschemanamen (außer der leeren Zeichenfolge) sind implementierungsdefiniert.

    – Jonathan Leffler

    26. April 2015 um 18:46 Uhr

Benutzeravatar von Carl Norum
Karl Norum

Wenn Ihr printf das unterstützt ' Flag (wie von POSIX 2008 gefordert printf()), können Sie dies wahrscheinlich tun, indem Sie Ihr Gebietsschema entsprechend einstellen. Beispiel:

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

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

Und bauen & ausführen:

$ ./example 
1,123,456,789

Getestet auf Mac OS X und Linux (Ubuntu 10.10).

  • Ich habe das auf getestet sprintf() in einem eingebetteten System und es funktioniert nicht (offensichtlich, weil es, wie Sie sagen, das Flag ‘ nicht unterstützt.

    – gbmhunter

    8. Mai 2013 um 2:32 Uhr

  • Ich bin sicher, dass Sie eine C-Bibliothek finden können, die dies ohne allzu große Probleme unterstützt.

    – Karl Norum

    8. Mai 2013 um 3:05 Uhr

  • Ich habe kurz nachgesehen, nichts Passendes gefunden und meine eigene mit einigen der oben genannten Ideen umgesetzt. Es wäre großartig, eine aktuelle Bibliothek zu finden, damit Sie sie für Floats und Strings mit Dezimalstellen verwenden können.

    – gbmhunter

    9. Mai 2013 um 10:40 Uhr

  • FWIW Atmel Studio‘s eingebettetes System printf() erscheint tragisch nicht die zu unterstützen ' Modifikator. Aus der Überschrift: Copyright ... 2007 Joerg Wunsch ... 1993 Regents of the University of California dh ein BSD-Derivat.

    – Bob Stein

    7. März 2014 um 16:25 Uhr

  • Dies ist zwar praktisch, aber Sie möchten den Status für diese Funktionalität nicht unbedingt ändern (setlocale).

    – Ideengeber42

    8. Oktober 2015 um 4:44 Uhr

Benutzeravatar von paxdiablo
paxdiablo

Sie können dies rekursiv wie folgt tun (Achtung INT_MIN Wenn Sie das Zweierkomplement verwenden, benötigen Sie zusätzlichen Code, um dies zu verwalten):

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);
}

Eine Zusammenfassung:

  • Benutzer ruft an printfcomma Bei einer Ganzzahl wird der Sonderfall negativer Zahlen behandelt, indem einfach “-” gedruckt und die Zahl positiv gemacht wird (dies ist das Bit, mit dem es nicht funktioniert INT_MIN).
  • Wenn Du eintrittst printfcomma2eine Zahl unter 1.000 wird einfach gedruckt und zurückgegeben.
  • Andernfalls wird die Rekursion auf der nächsthöheren Ebene aufgerufen (also wird 1.234.567 mit 1.234, dann 1 aufgerufen), bis eine Zahl kleiner als 1.000 gefunden wird.
  • Dann wird diese Zahl ausgegeben und wir gehen den Rekursionsbaum zurück, wobei wir ein Komma und die nächste Zahl drucken, während wir gehen.

Es gibt auch die prägnantere Version, obwohl sie bei der Überprüfung auf negative Zahlen unnötige Verarbeitung durchführt jeder Ebene (nicht, dass dies angesichts der begrenzten Anzahl von Rekursionsebenen von Bedeutung wäre). Dies ist ein vollständiges Programm zum Testen:

#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;
}

und die Ausgabe ist:

-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

Eine iterative Lösung für diejenigen, die der Rekursion nicht vertrauen (obwohl das einzige Problem bei der Rekursion eher der Stapelspeicher ist, der hier kein Problem darstellt, da er selbst für eine 64-Bit-Ganzzahl nur wenige Ebenen tief ist):

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);
    }
}

Beides generiert 2,147,483,647 zum INT_MAX.


Der gesamte obige Code dient zum Trennen von dreistelligen Gruppen durch Kommas, aber Sie können auch andere Zeichen verwenden, z. B. ein Leerzeichen:

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

void printfspace (int n) {
    if (n < 0) {
        printf ("-");
        n = -n;
    }
    printfspace2 (n);
}

Hier ist eine sehr einfache Implementierung. Diese Funktion enthält nein Fehlerüberprüfung, Puffergrößen müssen vom Aufrufer überprüft werden. Es funktioniert auch nicht für negative Zahlen. Solche Verbesserungen werden dem Leser als Übung überlassen.

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;
}

  • Ich mag dieses, es verwendet sprintf anstelle von printf, was für eingebettete Systeme nützlich ist.

    – gbmhunter

    8. Mai 2013 um 2:04 Uhr

  • Ganz nett, aber mit einigen kleinen Anpassungen, um für negative Zahlen zu arbeiten.

    – Ideengeber42

    17. Juli 2014 um 4:36 Uhr

  • (modifizierte Version zur Unterstützung negativer Zahlen stackoverflow.com/a/24795133/432509)

    – Ideengeber42

    8. Oktober 2015 um 4:37 Uhr

Eier! Ich mache das die ganze Zeit, benutze gcc/g++ und glibc unter Linux und ja, der ‘-Operator ist vielleicht kein Standard, aber ich mag seine Einfachheit.

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

int main()
{
    int bignum=12345678;

    setlocale(LC_ALL,"");

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

    return 0;
}

Gibt Ausgabe von:

Große Zahl: 12.345.678

Man muss sich nur an den ‘setlocale’-Aufruf erinnern, sonst wird nichts formatiert.

Benutzeravatar von Jerry Coffin
Jerry Sarg

Vielleicht wäre eine Locale-fähige Version interessant.

#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

Dies hat einen Fehler (aber einen, den ich für ziemlich geringfügig halten würde). Auf Zweierkomplement-Hardware konvertiert es die negativste Zahl nicht richtig, weil es versucht, eine negative Zahl in ihre entsprechende positive Zahl mit umzuwandeln N = -N; Im Zweierkomplement hat die maximal negative Zahl keine entsprechende positive Zahl, es sei denn, Sie heben sie auf einen größeren Typ hoch. Eine Möglichkeit, dies zu umgehen, besteht darin, die Nummer zum entsprechenden vorzeichenlosen Typ zu machen (aber das ist nicht trivial).

  • Ich habe eine Frage gestellt, die eher auf eine plattformübergreifende Implementierung des Formats abzielt '-flag hier: stackoverflow.com/q/44523855/2642059 Ich denke, diese Antwort spricht das perfekt an und führt jetzt weitere Tests durch. Wenn ja, sollte ich diese Frage als Dupe markieren, oder?

    – Jonathan Mei

    13. Juni 2017 um 14:40 Uhr


  • OK, als erstes habe ich festgestellt, dass es sich nicht anpasst, wenn sich das Gebietsschema anpasst. Warum pflegen, tsep, place_strund neg_str überhaupt? Warum nicht einfach direkt verwenden fmt_infos Mitglieder?

    – Jonathan Mei

    13. Juni 2017 um 17:18 Uhr

  • OK, Zahlensache Nummer 2, dieser Code kann keine negativen Zahlen verarbeiten … und ich weiß nicht genau, wie er könnte, while (*ptr-- = *neg_str++) macht für mich nicht viel Sinn. Sie fügen die negativen Zeichenfolgenzeichen in umgekehrter Reihenfolge ein.

    – Jonathan Mei

    13. Juni 2017 um 18:19 Uhr

  • Also … ich habe das Speicherleck beseitigt und den Fehler mit negativen Zahlen korrigiert: ideone.com/gTv8Z4 Leider gibt es immer noch ein Problem mit mehreren Trennzeichen oder negativen Symbolen mit mehreren Zeichen, die rückwärts in die Zeichenfolge geschrieben werden. Das versuche ich als nächstes zu lösen…

    – Jonathan Mei

    13. Juni 2017 um 19:23 Uhr

  • @JonathanMee: Ich habe den Code aktualisiert (und mindestens ein paar weitere Testfälle hinzugefügt, einschließlich negativer Zahlen).

    – Jerry Sarg

    14. Juni 2017 um 6:07 Uhr

Ohne Rekursion oder String-Handling, ein mathematischer Ansatz:

#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) ) ;
    }
}

Im Prinzip ähnlich der rekursiven Lösung von Pax, aber durch vorherige Berechnung der Größenordnung wird eine Rekursion vermieden (vielleicht mit erheblichen Kosten).

Beachten Sie auch, dass das tatsächliche Zeichen, das zum Trennen von Tausendern verwendet wird, gebietsschemaspezifisch ist.

Bearbeiten:Siehe die Kommentare von @Chux unten für Verbesserungen.

  • Ich habe eine Frage gestellt, die eher auf eine plattformübergreifende Implementierung des Formats abzielt '-flag hier: stackoverflow.com/q/44523855/2642059 Ich denke, diese Antwort spricht das perfekt an und führt jetzt weitere Tests durch. Wenn ja, sollte ich diese Frage als Dupe markieren, oder?

    – Jonathan Mei

    13. Juni 2017 um 14:40 Uhr


  • OK, als erstes habe ich festgestellt, dass es sich nicht anpasst, wenn sich das Gebietsschema anpasst. Warum pflegen, tsep, place_strund neg_str überhaupt? Warum nicht einfach direkt verwenden fmt_infos Mitglieder?

    – Jonathan Mei

    13. Juni 2017 um 17:18 Uhr

  • OK, Zahlensache Nummer 2, dieser Code kann keine negativen Zahlen verarbeiten … und ich weiß nicht genau, wie er könnte, while (*ptr-- = *neg_str++) macht für mich nicht viel Sinn. Sie fügen die negativen Zeichenfolgenzeichen in umgekehrter Reihenfolge ein.

    – Jonathan Mei

    13. Juni 2017 um 18:19 Uhr

  • Also … ich habe das Speicherleck beseitigt und den Fehler mit negativen Zahlen korrigiert: ideone.com/gTv8Z4 Leider gibt es immer noch ein Problem mit mehreren Trennzeichen oder negativen Symbolen mit mehreren Zeichen, die rückwärts in die Zeichenfolge geschrieben werden. Das versuche ich als nächstes zu lösen…

    – Jonathan Mei

    13. Juni 2017 um 19:23 Uhr

  • @JonathanMee: Ich habe den Code aktualisiert (und mindestens ein paar weitere Testfälle hinzugefügt, einschließlich negativer Zahlen).

    – Jerry Sarg

    14. Juni 2017 um 6:07 Uhr

Basierend auf @Greg Hewgill, berücksichtigt jedoch negative Zahlen und gibt die Zeichenfolgengröße zurück.

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);
}

1420900cookie-checkWie formatiere ich eine Zahl mit Komma als Tausendertrennzeichen in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy