Wie bestimme ich die Anzahl der Ziffern einer ganzen Zahl in C?

Lesezeit: 1 Minute

Benutzeravatar von willc2
willc2

zum Beispiel,

n = 3432, result 4

n = 45, result 2

n = 33215, result 5

n = -357, result 3

Ich denke, ich könnte es einfach in eine Zeichenfolge verwandeln und dann die Länge der Zeichenfolge ermitteln, aber das scheint verworren und hack-y zu sein.

  • Das Abrufen der Zeichenfolgenlänge würde bei negativen Zahlen fehlschlagen. Holen Sie sich also stattdessen die Länge des absoluten Werts. 😉

    – Wim ten Brink

    1. Juli 2009 um 12:50 Uhr

  • Char-Buff[100]; int r = sprintf(buff,”%s”,n) – (r<0);

    – paxdiablo

    1. Juli 2009 um 13:25 Uhr

  • Meinst du Dezimalzahlen? Dezimalstellen sind etwas, das reelle Zahlen haben, und ganze Zahlen nicht, per Definition.

    – Werden

    1. Juli 2009 um 13:38 Uhr

  • Äh … Pax, ist das ein juristischer Ausdruck? Da r vor der Zuweisung keinen Wert hat, wirkt der Teil „(r < 0)“ beängstigend. Oder vielleicht meinten Sie, dass es als zweiter Schritt gemacht werden sollte, also ist es nur die Notation, die ich nicht bekomme (ich lese es, als wäre es C).

    – abschalten

    1. Juli 2009 um 13:41 Uhr

  • Muss … daran denken … zu … Einheit … testen! Char-Buff[100]; int r = sprintf(buff,”%d”,n) – (n<0);

    – paxdiablo

    2. Juli 2009 um 4:33 Uhr

Der rekursive Ansatz 🙂

int numPlaces (int n) {
    if (n < 0) return numPlaces ((n == INT_MIN) ? INT_MAX: -n);
    if (n < 10) return 1;
    return 1 + numPlaces (n / 10);
}

Oder iterativ:

int numPlaces (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX: -n;
    while (n > 9) {
        n /= 10;
        r++;
    }
    return r;
}

Oder rohe Geschwindigkeit:

int numPlaces (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n < 10) return 1;
    if (n < 100) return 2;
    if (n < 1000) return 3;
    if (n < 10000) return 4;
    if (n < 100000) return 5;
    if (n < 1000000) return 6;
    if (n < 10000000) return 7;
    if (n < 100000000) return 8;
    if (n < 1000000000) return 9;
    /*      2147483647 is 2^31-1 - add more ifs as needed
       and adjust this final return as well. */
    return 10;
}

Die obigen wurden modifiziert, um MININT besser verarbeiten zu können. Auf allen seltsamen Systemen, die nicht vernünftig 2 folgenn Zweierkomplementregeln für ganze Zahlen, müssen sie möglicherweise weiter angepasst werden.

Die Rohgeschwindigkeitsversion übertrifft tatsächlich die unten modifizierte Gleitkommaversion:

int numPlaces (int n) {
    if (n == 0) return 1;
    return floor (log10 (abs (n))) + 1;
}

Mit hundert Millionen Iterationen erhalte ich folgende Ergebnisse:

Raw speed with 0:            0 seconds
Raw speed with 2^31-1:       1 second
Iterative with 2^31-1:       5 seconds
Recursive with 2^31-1:       6 seconds
Floating point with 1:       6 seconds
Floating point with 2^31-1:  7 seconds

Das hat mich tatsächlich ein wenig überrascht – ich dachte, die Intel-Chips hätten eine anständige FPU, aber ich denke, allgemeine FP-Operationen können immer noch nicht mit handoptimiertem Integer-Code mithalten.

Aktualisieren Sie die folgenden Vorschläge von Stormsoul:

Das Testen der multiplizieren-iterativen Lösung von Stormsoul ergibt ein Ergebnis von 4 Sekunden, so dass sie zwar viel schneller als die dividieren-iterative Lösung ist, aber immer noch nicht mit der optimierten if-Anweisungslösung übereinstimmt.

Durch die Auswahl der Argumente aus einem Pool von 1000 zufällig generierten Zahlen wurde die Zeit für die Rohgeschwindigkeit auf 2 Sekunden erhöht. Obwohl es den Anschein hat, dass es möglicherweise einen Vorteil hatte, jedes Mal dasselbe Argument zu haben, ist dies immer noch der schnellste aufgeführte Ansatz.

Das Kompilieren mit -O2 verbesserte die Geschwindigkeiten, aber nicht die relativen Positionen (ich habe die Anzahl der Iterationen um den Faktor zehn erhöht, um dies zu überprüfen).

Jede weitere Analyse muss sich ernsthaft mit dem Innenleben der CPU-Effizienz befassen (verschiedene Arten der Optimierung, Verwendung von Caches, Verzweigungsvorhersage, welche CPU Sie tatsächlich haben, die Umgebungstemperatur im Raum usw.). meiner bezahlten Arbeit im Weg stehen :-). Es war eine interessante Ablenkung, aber irgendwann wird der Return on Investment für die Optimierung zu gering, um eine Rolle zu spielen. Ich denke, wir haben genug Lösungen, um die Frage beantwortet zu haben (bei der es schließlich nicht um Geschwindigkeit ging).

Weitere Aktualisierung:

Dies wird mein letztes Update zu dieser Antwort sein, abgesehen von eklatanten Fehlern, die nicht von der Architektur abhängen. Inspiriert von Stormsouls tapferen Messbemühungen poste ich mein Testprogramm (modifiziert nach Stormsouls eigenem Testprogramm) zusammen mit einigen Beispielzahlen für alle Methoden, die hier in den Antworten gezeigt werden. Denken Sie daran, dass dies aktiviert ist eine bestimmte Maschinekann Ihr Kilometerstand variieren, je nachdem, wo Sie es ausführen (weshalb ich den Testcode poste).

Machen Sie damit, was Sie wollen:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <time.h>

#define numof(a) (sizeof(a) / sizeof(a[0]))

/* Random numbers and accuracy checks. */

static int rndnum[10000];
static int rt[numof(rndnum)];

/* All digit counting functions here. */

static int count_recur (int n) {
    if (n < 0) return count_recur ((n == INT_MIN) ? INT_MAX : -n);
    if (n < 10) return 1;
    return 1 + count_recur (n / 10);
}

static int count_diviter (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    while (n > 9) {
        n /= 10;
        r++;
    }
    return r;
}

static int count_multiter (int n) {
    unsigned int num = abs(n);
    unsigned int x, i;
    for (x=10, i=1; ; x*=10, i++) {
        if (num < x)
            return i;
        if (x > INT_MAX/10)
            return i+1;
    }
}

static int count_ifs (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n < 10) return 1;
    if (n < 100) return 2;
    if (n < 1000) return 3;
    if (n < 10000) return 4;
    if (n < 100000) return 5;
    if (n < 1000000) return 6;
    if (n < 10000000) return 7;
    if (n < 100000000) return 8;
    if (n < 1000000000) return 9;
    /*      2147483647 is 2^31-1 - add more ifs as needed
    and adjust this final return as well. */
    return 10;
}

static int count_revifs (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n > 999999999) return 10;
    if (n > 99999999) return 9;
    if (n > 9999999) return 8;
    if (n > 999999) return 7;
    if (n > 99999) return 6;
    if (n > 9999) return 5;
    if (n > 999) return 4;
    if (n > 99) return 3;
    if (n > 9) return 2;
    return 1;
}

static int count_log10 (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n == 0) return 1;
    return floor (log10 (n)) + 1;
}

static int count_bchop (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n >= 100000000) {
        r += 8;
        n /= 100000000;
    }
    if (n >= 10000) {
        r += 4;
        n /= 10000;
    }
    if (n >= 100) {
        r += 2;
        n /= 100;
    }
    if (n >= 10)
        r++;

    return r;
}

/* Structure to control calling of functions. */

typedef struct {
    int (*fnptr)(int);
    char *desc;
} tFn;

static tFn fn[] = {
    NULL,                              NULL,
    count_recur,    "            recursive",
    count_diviter,  "     divide-iterative",
    count_multiter, "   multiply-iterative",
    count_ifs,      "        if-statements",
    count_revifs,   "reverse-if-statements",
    count_log10,    "               log-10",
    count_bchop,    "          binary chop",
};
static clock_t clk[numof (fn)];

int main (int c, char *v[]) {
    int i, j, k, r;
    int s = 1;

    /* Test code:
        printf ("%11d %d\n", INT_MIN, count_recur(INT_MIN));
        for (i = -1000000000; i != 0; i /= 10)
            printf ("%11d %d\n", i, count_recur(i));
        printf ("%11d %d\n", 0, count_recur(0));
        for (i = 1; i != 1000000000; i *= 10)
            printf ("%11d %d\n", i, count_recur(i));
        printf ("%11d %d\n", 1000000000, count_recur(1000000000));
        printf ("%11d %d\n", INT_MAX, count_recur(INT_MAX));
    /* */

    /* Randomize and create random pool of numbers. */

    srand (time (NULL));
    for (j = 0; j < numof (rndnum); j++) {
        rndnum[j] = s * rand();
        s = -s;
    }
    rndnum[0] = INT_MAX;
    rndnum[1] = INT_MIN;

    /* For testing. */
    for (k = 0; k < numof (rndnum); k++) {
        rt[k] = (fn[1].fnptr)(rndnum[k]);
    }

    /* Test each of the functions in turn. */

    clk[0] = clock();
    for (i = 1; i < numof (fn); i++) {
        for (j = 0; j < 10000; j++) {
            for (k = 0; k < numof (rndnum); k++) {
                r = (fn[i].fnptr)(rndnum[k]);
                /* Test code:
                    if (r != rt[k]) {
                        printf ("Mismatch error [%s] %d %d %d %d\n",
                            fn[i].desc, k, rndnum[k], rt[k], r);
                        return 1;
                    }
                /* */
            }
        }
        clk[i] = clock();
    }

    /* Print out results. */

    for (i = 1; i < numof (fn); i++) {
        printf ("Time for %s: %10d\n", fn[i].desc, (int)(clk[i] - clk[i-1]));
    }

    return 0;
}

Denken Sie daran, dass Sie sicherstellen müssen, dass Sie die richtige Befehlszeile zum Kompilieren verwenden. Insbesondere Sie kann müssen die zu erhaltende mathematische Bibliothek explizit auflisten log10() Arbeiten. Die Befehlszeile, die ich unter Debian verwendet habe, war gcc -o testprog testprog.c -lm.

Und was die Ergebnisse betrifft, hier ist die Rangliste für mein Umfeld:

Optimierungsstufe 0:

Time for reverse-if-statements:       1704
Time for         if-statements:       2296
Time for           binary chop:       2515
Time for    multiply-iterative:       5141
Time for      divide-iterative:       7375
Time for             recursive:      10469
Time for                log-10:      26953

Optimierungsstufe 3:

Time for         if-statements:       1047
Time for           binary chop:       1156
Time for reverse-if-statements:       1500
Time for    multiply-iterative:       2937
Time for      divide-iterative:       5391
Time for             recursive:       8875
Time for                log-10:      25438

  • Die rekursive Version scheint mir die sauberste, einfachste und beste selbstdokumentierende Lösung zu sein, die veröffentlicht wurde.

    Benutzer447688

    1. Juli 2009 um 13:06 Uhr

  • @moogs, Sie können jede der in dieser oder einer anderen Antwort hier vorgestellten Lösungen verwenden. Der Geschwindigkeitstest war wirklich nur eine Nebensache (die außer Kontrolle geraten ist). Und in jedem Fall haben Sie diese Zeit noch zur Verfügung – es ist nur mein Zeit, die hier möglicherweise verschwendet wurde, also fühlen Sie sich frei, die Früchte meiner Arbeit zu verwenden, wie Sie es wünschen 🙂

    – paxdiablo

    2. Juli 2009 um 2:34 Uhr

  • Ein kleiner Tipp zur Leistung: Wenn Sie wissen, dass ein Wert immer nicht negativ ist, verwenden Sie unsignierte Typen. Sie sind etwas schneller zu multiplizieren und zu dividieren. Der Compiler könnte für Sie vermuten, dass eine Variable niemals negativ ist, und diese Optimierung automatisch vornehmen, aber auch das ist möglicherweise nicht der Fall. In komplexeren Situationen tut es das nie.

    – Sturmseele

    2. Juli 2009 um 10:46 Uhr

  • Recht. Jemand im IRC hat einige Leistungstests durchgeführt, dann hat er unsigned verwendet und er hat einen wirklich großartigen Boost gegoogelt. Ich fordere Sie auf, die unsignierte Welt auszuprobieren! 🙂

    – Johannes Schaub – litb

    2. Juli 2009 um 14:05 Uhr

  • Schöne Antwort =) Ich mag die Rohgeschwindigkeitsversion, aber ich denke, sie kann durch binäre Verzweigung verbessert werden, um die Anzahl der Vergleiche im schlimmsten Fall auf vier zu reduzieren (ohne Berücksichtigung des Negativtests), offensichtlich auf Kosten der Lesbarkeit . Insofern kann man eine Version davon speziell auf den beabsichtigten Datenbereich abstimmen.

    – Reisfeld

    26. Oktober 2012 um 3:13 Uhr

Benutzeravatar von eduffy
eduffy

floor (log10 (abs (x))) + 1

http://en.wikipedia.org/wiki/Logarithmus

  • Das wäre unnötig langsam. Verwenden Sie nicht ohne Grund teure Funktionen wie log10(). Die schnelle Integer-Funktion ist einfach genug, um sie zu schreiben.

    – Sturmseele

    1. Juli 2009 um 14:31 Uhr

  • Geez .. betreiben Sie Leute immer noch einen 8088? Wen interessieren ein paar zusätzliche Taktzyklen. Paz brauchte 100.000.000 Iterationen, um einen messbaren Unterschied zu machen, und selbst das war vernachlässigbar! 6 Sekunden! Whoop-dee-do .. machen Sie mit Ihrem Leben weiter. Nächstes Jahr werden es 3 Sekunden sein.

    – eduffy

    1. Juli 2009 um 15:07 Uhr

  • @eduffy: Eine Millisekunde hier, eine Millisekunde da … und plötzlich spürt der Benutzer eine merkliche Verzögerung, nachdem er auf eine Schaltfläche geklickt hat. Im Ernst, diese kleinen Ineffizienzen summieren sich. Warum Taktzyklen verschwenden, wenn man damit nichts gewinnt?

    – Sturmseele

    1. Juli 2009 um 15:30 Uhr

  • @eduffy: Wenn dies auf einem eingebetteten Prozessor laufen würde, gäbe es möglicherweise keine Gleitkommaunterstützung, geschweige denn Protokollfunktionen, und die Taktrate könnte nur im zweistelligen MHz-Bereich liegen – also wäre eine vollständig auf Ganzzahlen basierende Lösung definitiv die bevorzugte Option.

    – Steve Melnikoff

    1. Juli 2009 um 16:12 Uhr

  • Es stellt sich heraus, dass die einfache Division zwar für kleine Werte schneller ist, der Logarithmus jedoch viel besser skaliert. Wenn Sie die Divisionsalgorithmen mit jedem Int von MIN_INT bis MAX_INT aufrufen (und dies die gleichen 100 Millionen Mal wie in den Beispielen von Paz wiederholen), erhalten Sie am Ende durchschnittlich 13,337 Sekunden pro Aufruf. Das Gleiche mit Logarithmus zu tun, ist durchschnittlich 8,143 Sekunden, die Rekursion dauert 11,971 Sekunden, und die kaskadierenden If-Anweisungen dauern durchschnittlich 0,953 Sekunden. Die nach Daily-WTF aussehende Lösung ist also eine Größenordnung schneller, aber auf lange Sicht liegt sie auf dem zweiten Platz.

    – Matt Poush

    1. Juli 2009 um 18:55 Uhr

Die kürzeste Antwort: snprintf(0,0,"%+d",n)-1

  • snprintf mit n=0 speichert nichts und erlaubt einen Null-Pufferzeiger; der Rückgabewert ist die Anzahl der Zeichen, die geschrieben worden wären. Das + Modifikator wird verwendet, um ein Zeichen zu drucken (+ oder -) auch wenn der Wert nicht negativ ist; Durch Subtrahieren von eins vom Ergebnis wird das Vorzeichen nicht als Ziffer gezählt.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    13. April 2015 um 5:29 Uhr

  • Von meinem (Debian Linux) System man-Seite auf snprintf: „Zum Rückgabewert von snprintf(), SUSv2 und C99 widersprechen sich: wann snprintf() wird mit gerufen size=0 [size is the second argument above] dann schreibt SUSv2 einen unspezifizierten Rückgabewert kleiner 1 vor, während C99 str in diesem Fall NULL zulässt und als Rückgabewert (wie immer) die Anzahl der Zeichen zurückgibt, die geschrieben worden wären, falls der Ausgabestring groß genug gewesen wäre .” {SUS = Einzelne UNIX-Spezifikation}

    – SlySven

    22. Juli 2016 um 21:59 Uhr


  • @SlySven: SUSv2 ist uralt und irrelevant.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    22. Juli 2016 um 22:26 Uhr

  • Nun, Debian ist dafür bekannt, etwas konservativ zu sein und nicht die schnellsten zu sein, die neue Dinge an Bord nehmen. 8-P Vielen Dank für Ihre Antwort – ich habe es in einem FOSS-Projekt verwendet, für das ich codiere (und es hier entsprechend zugeschrieben) …!

    – SlySven

    24. Juli 2016 um 4:29 Uhr

  • @SlySven: Das stammt nicht von Debian AFAIK, sondern nur vom Linux-Manpages-Projekt. Ich glaube nicht, dass es jemals eine Linux-Libc mit dem falschen gab snprintf Verhalten, aber es mag ein paar alte proprietäre Unices und MSVCs gegeben haben _snprintf hatte den bug auch.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    24. Juli 2016 um 15:38 Uhr

Benutzeravatar von lakshmanaraj
lakshmanaraj

Pseudoalgorithmus für die binäre Suche, um die Anzahl der Ziffern von r in v zu ermitteln.

if (v < 0 ) v=-v;

r=1;

if (v >= 100000000)
{
  r+=8;
  v/=100000000;
}

if (v >= 10000) {
    r+=4;
    v/=10000;
}

if (v >= 100) {
    r+=2;
    v/=100;
}

if( v>=10)
{
    r+=1;
}

return r;

Benutzeravatar von vitaut
vitaut

Hier ist eine sehr schnelle Methode, um die Anzahl der Dezimalstellen zu berechnen von Kendall Willets:

int count_digits(uint32_t n) {
#ifndef __has_builtin
#  define __has_builtin(x) 0
#endif
#if __has_builtin(__builtin_clz)
  // This increments the upper 32 bits (log10(T) - 1) when >= T is added.
  #  define K(T) (((sizeof(#T) - 1ull) << 32) - T)
  static const uint64_t table[] = {
      K(0),          K(0),          K(0),           // 8
      K(10),         K(10),         K(10),          // 64
      K(100),        K(100),        K(100),         // 512
      K(1000),       K(1000),       K(1000),        // 4096
      K(10000),      K(10000),      K(10000),       // 32k
      K(100000),     K(100000),     K(100000),      // 256k
      K(1000000),    K(1000000),    K(1000000),     // 2048k
      K(10000000),   K(10000000),   K(10000000),    // 16M
      K(100000000),  K(100000000),  K(100000000),   // 128M
      K(1000000000), K(1000000000), K(1000000000),  // 1024M
      K(1000000000), K(1000000000)                  // 4B
  };
  return (n + table[__builtin_clz(n | 1) ^ 31]) >> 32u;
#else
  int count = 1;
  for (;;) {
    if (n < 10) return count;
    if (n < 100) return count + 1;
    if (n < 1000) return count + 2;
    if (n < 10000) return count + 3;
    n /= 10000u;
    count += 4;
  }
  return count;
#endif
}

Der schnelle Weg setzt auf __builtin_clz das ist in GCC und Clang verfügbar, funktioniert aber dank des Fallbacks einigermaßen gut count_digits ist voll tragbar.

Dies generiert sehr effizienten Code (Gottriegel):

count_digits(unsigned int):
  mov edx, edi
  mov eax, edi
  or edx, 1
  bsr edx, edx
  movsx rdx, edx
  add rax, QWORD PTR count_digits(unsigned int)::table[0+rdx*8]
  shr rax, 32
  ret

  • #if defined(__has_builtin) && __has_builtin(__builtin_clz) ist NICHT tragbar.

    – Greg A. Woods

    14. Juni 2021 um 3:41 Uhr


  • Auch Ihr Ersatz für int_log2_64() ist für die Variante völlig falsch nicht verwenden __builtin_clz()! Vielleicht wäre es besser, wenn Sie einfach den Originalcode kopieren und einfügen, anstatt zu versuchen, ihn zu hacken.

    – Greg A. Woods

    16. Juni 2021 um 20:31 Uhr

  • @GregA.Woods warum nicht?

    – vitaut

    17. Juni 2021 um 2:54 Uhr

  • Warum nicht was?

    – Greg A. Woods

    17. Juni 2021 um 4:25 Uhr

  • Ah, ja, tut mir leid — Sie haben Willits Originalcode so sehr verändert, dass ich übersehen habe, dass Sie nicht versucht haben, einen tragbareren Code bereitzustellen int_log2_64()sondern ersetzten stattdessen nur alle count_digits() für Compiler ohne die eingebaute Funktion.

    – Greg A. Woods

    19. Juni 2021 um 0:10 Uhr

Teilen Sie in einer Schleife durch 10, bis das Ergebnis Null erreicht. Die Anzahl der Iterationen entspricht der Anzahl der Dezimalstellen.

Angenommen, Sie erwarten 0 Ziffern in einem Nullwert:

int countDigits( int value )
{
    int result = 0;
    while( value != 0 ) {
       value /= 10;
       result++;
    }
    return result;
}

  • #if defined(__has_builtin) && __has_builtin(__builtin_clz) ist NICHT tragbar.

    – Greg A. Woods

    14. Juni 2021 um 3:41 Uhr


  • Auch Ihr Ersatz für int_log2_64() ist für die Variante völlig falsch nicht verwenden __builtin_clz()! Vielleicht wäre es besser, wenn Sie einfach den Originalcode kopieren und einfügen, anstatt zu versuchen, ihn zu hacken.

    – Greg A. Woods

    16. Juni 2021 um 20:31 Uhr

  • @GregA.Woods warum nicht?

    – vitaut

    17. Juni 2021 um 2:54 Uhr

  • Warum nicht was?

    – Greg A. Woods

    17. Juni 2021 um 4:25 Uhr

  • Ah, ja, tut mir leid — Sie haben Willits Originalcode so sehr verändert, dass ich übersehen habe, dass Sie nicht versucht haben, einen tragbareren Code bereitzustellen int_log2_64()sondern ersetzten stattdessen nur alle count_digits() für Compiler ohne die eingebaute Funktion.

    – Greg A. Woods

    19. Juni 2021 um 0:10 Uhr

Version mit konstanten Kosten, die x86-Assembly und eine Nachschlagetabelle verwendet:

int count_bsr(int i) {
    struct {
            int max;
            int count;
    } static digits[32] = {
            { 9, 1 }, { 9, 1 }, { 9, 1 }, { 9, 1 },
            { 99, 2 }, { 99, 2 }, { 99, 2 },
            { 999, 3 }, { 999, 3 }, { 999, 3 },
            { 9999, 4 }, { 9999, 4 }, { 9999, 4 }, { 9999, 4 },
            { 99999, 5 }, { 99999, 5 }, { 99999, 5 },
            { 999999, 6 }, { 999999, 6 }, { 999999, 6 },
            { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 },
            { 99999999, 8 }, { 99999999, 8 }, { 99999999, 8 },
            { 999999999, 9 }, { 999999999, 9 }, { 999999999, 9 },
            { INT_MAX, 10 }, { INT_MAX, 10 }
    };
        register const int z = 0;
        register unsigned log2;
        if (i < 0) i = -i;
        __asm__ __volatile__ (
                "bsr %1, %0;"  \
                "cmovz %2, %0;"\
                : "=r" (log2)  \
                : "rm" (i), "r"(z));
        return digits[log2].count + ( i > digits[log2].max );
}

Eine weitere mit einer kleineren Nachschlagetabelle und einer log10-Näherung, aus der entnommen wurde hier.

int count_bsr2( int i ) {
    static const unsigned limits[] =
            {0, 10, 100, 1000, 10000, 100000,
             1000000, 10000000, 100000000, 1000000000};
        register const int z = 0;
        register int l, log2;
        if (i < 0) i = -i;
        __asm__ __volatile__ (
                "bsr %1, %0;"  \
                "cmovz %2, %0;"\
                : "=r" (log2)  \
                : "rm" (i), "r"(z));
       l = (log2 + 1) * 1233 >> 12;
       return (l + ((unsigned)i >= limits[l]));
}

Beide nutzen die Tatsache, dass auf x86 -INT_MIN gleich INT_MIN ist.

Aktualisieren:

Gemäß Vorschlag sind hier die Zeiten für die count_bsr und nur ein etwas schnelleres 64-Bit count_bsr_mod Routinen im Vergleich zu den binären Such- und binären Chop-Algorithmen, die das sehr nette Testprogramm von Paxdiablo verwenden, das modifiziert wurde, um Sätze mit einer zufälligen Vorzeichenverteilung zu erzeugen. Tests wurden mit gcc 4.9.2 unter Verwendung der Optionen „-O3 -falign-functions=16 -falign-jumps=16 -march=corei7-avx“ erstellt und auf einem ansonsten ruhenden Sandy-Bridge-System mit deaktiviertem Turbo- und Ruhezustand ausgeführt.

Time for               bsr mod:     270000  
Time for                   bsr:     340000  
Time for           binary chop:     800000  
Time for         binary search:     770000  
Time for     binary search mod:     470000  

Quelle für den Test,

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <time.h>

#define numof(a) (sizeof(a) / sizeof(a[0]))

/* Random numbers and accuracy checks. */

static int rndnum[10000];
static int rt[numof(rndnum)];

/* All digit counting functions here. */

static int count_bchop (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n >= 100000000) {
        r += 8;
        n /= 100000000;
    }
    if (n >= 10000) {
        r += 4;
        n /= 10000;
    }
    if (n >= 100) {
        r += 2;
        n /= 100;
    }
    if (n >= 10)
        r++;

    return r;
}

static int count_bsearch(int i)
{
    if (i < 0)
    {
        if (i == INT_MIN)
            return 11; // special case for -2^31 because 2^31 can't fit in a two's complement 32-bit integer
        i = -i;
    }
    if              (i < 100000) {
        if          (i < 1000) {
            if      (i < 10)         return 1;
            else if (i < 100)        return 2;
            else                     return 3;
        } else {
            if      (i < 10000)      return 4;
            else                     return 5;
        }
    } else {
        if          (i < 10000000) {
            if      (i < 1000000)    return 6;
            else                     return 7;
        } else {
            if      (i < 100000000)  return 8;
            else if (i < 1000000000) return 9;
            else                     return 10;
        }
    }
}

// Integer log base 10, modified binary search.
static int count_bsearch_mod(int i) {
   unsigned x = (i >= 0) ? i : -i;
   if (x > 99)
      if (x > 999999)
         if (x > 99999999)
            return 9 + (x > 999999999);
         else
            return 7 + (x > 9999999);
      else
         if (x > 9999)
            return 5 + (x > 99999);
         else
            return 3 + (x > 999);
   else
         return 1 + (x > 9);
}

static int count_bsr_mod(int i) {
    struct {
            int m_count;
            int m_threshold;
    } static digits[32] =
    {
      { 1, 9 }, { 1, 9 }, { 1, 9 }, { 1, 9 },
      { 2, 99 }, { 2, 99 }, { 2, 99 },
      { 3, 999 }, { 3, 999 }, { 3, 999 },
      { 4, 9999 }, { 4, 9999 }, { 4, 9999 }, { 4, 9999 },
      { 5, 99999 }, { 5, 99999 }, { 5, 99999 },
      { 6, 999999 }, { 6, 999999 }, { 6, 999999 },
      { 7, 9999999 }, { 7, 9999999 }, { 7, 9999999 }, { 7, 9999999 },
      { 8, 99999999 }, { 8, 99999999 }, { 8, 99999999 },
      { 9, 999999999 }, { 9, 999999999 }, { 9, 999999999 },
      { 10, INT_MAX }, { 10, INT_MAX }
    };
        __asm__ __volatile__ (
            "cdq                    \n\t"
            "xorl %%edx, %0         \n\t"
            "subl %%edx, %0         \n\t"
            "movl %0, %%edx         \n\t"
            "bsrl %0, %0            \n\t"
            "shlq $32, %%rdx        \n\t"
            "movq %P1(,%q0,8), %q0  \n\t"
            "cmpq %q0, %%rdx        \n\t"
            "setg %%dl              \n\t"
            "addl %%edx, %0         \n\t"
                : "+a"(i)
                : "i"(digits)
                : "rdx", "cc"
        );
    return i;
}

static int count_bsr(int i) {
    struct {
            int max;
            int count;
    } static digits[32] = {
            { 9, 1 }, { 9, 1 }, { 9, 1 }, { 9, 1 },
            { 99, 2 }, { 99, 2 }, { 99, 2 },
            { 999, 3 }, { 999, 3 }, { 999, 3 },
            { 9999, 4 }, { 9999, 4 }, { 9999, 4 }, { 9999, 4 },
            { 99999, 5 }, { 99999, 5 }, { 99999, 5 },
            { 999999, 6 }, { 999999, 6 }, { 999999, 6 },
            { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 },
            { 99999999, 8 }, { 99999999, 8 }, { 99999999, 8 },
            { 999999999, 9 }, { 999999999, 9 }, { 999999999, 9 },
            { INT_MAX, 10 }, { INT_MAX, 10 }
    };
        register const int z = 0;
        register unsigned log2;
        if (i < 0) i = -i;
        __asm__ __volatile__ (
                "bsr %1, %0;"  \
                "cmovz %2, %0;"\
                : "=r" (log2)  \
                : "rm" (i), "r"(z));
        return digits[log2].count + ( i > digits[log2].max );
}

/* Structure to control calling of functions. */

typedef struct {
    int (*fnptr)(int);
    const char *desc;
} tFn;

static tFn fn[] = {
 {   NULL,                              NULL },
 {   count_bsr_mod,  "              bsr mod" },
 {   count_bsr,      "                  bsr" },
 {   count_bchop,    "          binary chop" },
 {   count_bsearch,  "        binary search" },
 {   count_bsearch_mod,"    binary search mod"}
};
static clock_t clk[numof (fn)];

int main (int c, char *v[]) {
    int i, j, k, r;
    int s = 1;

    /* Test code:
        printf ("%11d %d\n", INT_MIN, count_bsearch(INT_MIN));
        //for (i = -1000000000; i != 0; i /= 10)
        for (i = -999999999; i != 0; i /= 10)
            printf ("%11d %d\n", i, count_bsearch(i));
        printf ("%11d %d\n", 0, count_bsearch(0));
        for (i = 1; i != 1000000000; i *= 10)
            printf ("%11d %d\n", i, count_bsearch(i));
        printf ("%11d %d\n", 1000000000, count_bsearch(1000000000));
        printf ("%11d %d\n", INT_MAX, count_bsearch(INT_MAX));
    return 0;
    /* */

    /* Randomize and create random pool of numbers. */

    int p, n;
    p = n = 0;
    srand (time (NULL));
    for (j = 0; j < numof (rndnum); j++) {
        rndnum[j] = ((rand() & 2) - 1) * rand();
    }
    rndnum[0] = INT_MAX;
    rndnum[1] = INT_MIN;

    /* For testing. */
    for (k = 0; k < numof (rndnum); k++) {
        rt[k] = (fn[1].fnptr)(rndnum[k]);
    }

    /* Test each of the functions in turn. */

    clk[0] = clock();
    for (i = 1; i < numof (fn); i++) {
        for (j = 0; j < 10000; j++) {
            for (k = 0; k < numof (rndnum); k++) {
                r = (fn[i].fnptr)(rndnum[k]);
                /* Test code:
                    if (r != rt[k]) {
                        printf ("Mismatch error [%s] %d %d %d %d\n",
                            fn[i].desc, k, rndnum[k], rt[k], r);
                        return 1;
                    }
                /* */
            }
        }
        clk[i] = clock();
    }

    /* Print out results. */

    for (i = 1; i < numof (fn); i++) {
        printf ("Time for %s: %10d\n", fn[i].desc, (int)(clk[i] - clk[i-1]));
    }

    return 0;
}

  • +1 für die geekigste Antwort. Sie sollten Leistungszahlen hinzufügen, um zu zeigen, wie gut es funktioniert, insbesondere im Vergleich zu Binary Chop.

    – CodeManX

    16. August 2015 um 21:04 Uhr

  • Es ist ein Fehler drin count_bsearch(): für die Semantik des OP sollte es zurückkehren 10 zum i == INT_MIN.

    – chqrlie

    19. Februar 2017 um 3:45 Uhr

  • -i hat undefiniertes Verhalten für INT_MIN, für signed int i. Verwenden unsigned absval = 0U - i (oder i falls positiv), um es in C zu vermeiden, aber dennoch effizient auf die gleiche Asm für die Negation zu kompilieren. Es sei denn, Sie kompilieren mit -fwrapvhandelt es sich eher um eine “passiert-funktioniert”-Situation als um die sichere Übernahme des Verhaltens der ISA, auf die Sie abzielen.

    – Peter Cordes

    20. Dezember 2020 um 5:06 Uhr


1419680cookie-checkWie bestimme ich die Anzahl der Ziffern einer ganzen Zahl in C?

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

Privacy policy