webentwicklung-frage-antwort-db.com.de

wie man __uint128_t nummer mit gcc druckt?

Gibt es PRIu128, der sich ähnlich wie PRIu64 von <inttypes.h> verhält:

printf("%" PRIu64 "\n", some_uint64_value);

Oder manuell Ziffer für Ziffer umwandeln:

int print_uint128(uint128_t n) {
  if (n == 0)  return printf("0\n");

  char str[40] = {0}; // log10(1 << 128) + '\0'
  char *s = str + sizeof(str) - 1; // start at the end
  while (n != 0) {
    if (s == str) return -1; // never happens

    *--s = "0123456789"[n % 10]; // save last digit
    n /= 10;                     // drop it
  }
  return printf("%s\n", s);
}

ist die einzige Option?

Beachten Sie, dass uint128_t meine eigene Typedef für __uint128_t ist.

45
jfs

Nein, in der Bibliothek gibt es keine Unterstützung für das Drucken dieser Typen. Sie sind nicht einmal erweiterte Integer-Typen im Sinne des C-Standards.

Ihre Idee, den Druck von hinten zu beginnen, ist gut, aber Sie könnten viel größere Brocken verwenden. In einigen Tests für P99 habe ich eine solche Funktion, die verwendet 

uint64_t const d19 = UINT64_C(10000000000000000000);

als die größte Leistung von 10, die in einen uint64_t passt.

Als Dezimalzahl werden diese großen Zahlen sehr bald unlesbar. Daher besteht eine andere, einfachere Option darin, sie im Hex-Format zu drucken. Dann kannst du sowas machen

  uint64_t low = (uint64_t)x;
  // This is UINT64_MAX, the largest number in 64 bit
  // so the longest string that the lower half can occupy
  char buf[] = { "18446744073709551615" };
  sprintf(buf, "%" PRIX64, low);

um die untere Hälfte zu bekommen und dann im Grunde das gleiche mit

  uint64_t high = (x >> 64);

für die obere hälfte.

14
Jens Gustedt

Das GCC 4.7.1 Handbuch sagt:

6,8 128-Bit-Ganzzahlen

Als Erweiterung wird der Integer-Skalartyp __int128 für Ziele mit einer Integerzahl .__ unterstützt. Modus breit genug für 128 Bit. Schreiben Sie einfach __int128 für eine 128-Bit-Ganzzahl mit Vorzeichen oder unsigned __int128 für eine vorzeichenlose 128-Bit-Ganzzahl. Es gibt keine Unterstützung in GCC zum Ausdruck von eine Ganzzahlkonstante vom Typ __int128 für Ziele mit long long-Ganzzahl mit weniger als [sic] 128 Bit Breite.

Obwohl __uint128_t nicht erwähnt wird, wird dieser Typ auch mit strengen Warnhinweisen akzeptiert:

#include <stdio.h>

int main(void)
{
    __uint128_t u128 = 12345678900987654321;
    printf("%llx\n", (unsigned long long)(u128 & 0xFFFFFFFFFFFFFFFF));
    return(0);
}

Zusammenstellung:

$ gcc -O3 -g -std=c99 -Wall -Wextra -pedantic xxx.c -o xxx  
xxx.c: In function ‘main’:
xxx.c:6:24: warning: integer constant is so large that it is unsigned [enabled by default]
$

(Dies ist mit einem selbstkompilierten GCC 4.7.1 unter Mac OS X 10.7.4.)

Ändern Sie die Konstante in 0x12345678900987654321 und der Compiler sagt:

xxx.c: In function ‘main’:
xxx.c:6:24: warning: integer constant is too large for its type [enabled by default]

Es ist also nicht leicht, diese Kreaturen zu manipulieren. Die Ausgaben mit der Dezimalkonstante und den Hex-Konstanten sind:

ab54a98cdc6770b1
5678900987654321

Beim Drucken in Dezimalzahlen ist es am besten zu prüfen, ob der Wert größer als UINT64_MAX ist. Wenn dies der Fall ist, dividieren Sie durch die größte Potenz von 10, die kleiner als UINT64_MAX ist, drucken Sie diese Zahl aus (und müssen Sie den Vorgang möglicherweise ein zweites Mal wiederholen), und drucken Sie dann den Rückstand modulo mit der größten Potenz von 10, die kleiner als ist UINT64_MAX, denken Sie daran, mit führenden Nullen aufgefüllt zu werden.

Dies führt zu etwas wie:

#include <stdio.h>
#include <inttypes.h>

/*
** Using documented GCC type unsigned __int128 instead of undocumented
** obsolescent typedef name __uint128_t.  Works with GCC 4.7.1 but not
** GCC 4.1.2 (but __uint128_t works with GCC 4.1.2) on Mac OS X 10.7.4.
*/
typedef unsigned __int128 uint128_t;

/*      UINT64_MAX 18446744073709551615ULL */
#define P10_UINT64 10000000000000000000ULL   /* 19 zeroes */
#define E10_UINT64 19

#define STRINGIZER(x)   # x
#define TO_STRING(x)    STRINGIZER(x)

static int print_u128_u(uint128_t u128)
{
    int rc;
    if (u128 > UINT64_MAX)
    {
        uint128_t leading  = u128 / P10_UINT64;
        uint64_t  trailing = u128 % P10_UINT64;
        rc = print_u128_u(leading);
        rc += printf("%." TO_STRING(E10_UINT64) PRIu64, trailing);
    }
    else
    {
        uint64_t u64 = u128;
        rc = printf("%" PRIu64, u64);
    }
    return rc;
}

int main(void)
{
    uint128_t u128a = ((uint128_t)UINT64_MAX + 1) * 0x1234567890ABCDEFULL +
                      0xFEDCBA9876543210ULL;
    uint128_t u128b = ((uint128_t)UINT64_MAX + 1) * 0xF234567890ABCDEFULL +
                      0x1EDCBA987654320FULL;
    int ndigits = print_u128_u(u128a);
    printf("\n%d digits\n", ndigits);
    ndigits = print_u128_u(u128b);
    printf("\n%d digits\n", ndigits);
    return(0);
}

Die Ausgabe davon ist:

24197857200151252746022455506638221840
38 digits
321944928255972408260334335944939549199
39 digits

Wir können dies mit bc überprüfen:

$ bc
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
ibase = 16
1234567890ABCDEFFEDCBA9876543210
24197857200151252746022455506638221840
F234567890ABCDEF1EDCBA987654320F
321944928255972408260334335944939549199
quit
$

Für Hex ist der Prozess eindeutig einfacher. Sie können in nur zwei Schritten verschieben, maskieren und drucken. Für Oktal müssen Sie, da 64 kein Vielfaches von 3 ist, analoge Schritte zur Dezimaloperation ausführen.

Die print_u128_u()-Schnittstelle ist nicht ideal, gibt aber mindestens die Anzahl der gedruckten Zeichen zurück, genau wie printf(). Das Anpassen des Codes zum Formatieren des Ergebnisses in einen Zeichenfolgenpuffer ist keine ganz unbedeutende Übung in der Programmierung, aber nicht furchtbar schwierig.

28

Ich habe keine eingebaute Lösung, aber das Aufteilen/Modul ist teuer. Sie können binär in Dezimalzahlen umwandeln.

static char *qtoa(uint128_t n) {
    static char buf[40];
    unsigned int i, j, m = 39;
    memset(buf, 0, 40);
    for (i = 128; i-- > 0;) {
        int carry = !!(n & ((uint128_t)1 << i));
        for (j = 39; j-- > m + 1 || carry;) {
            int d = 2 * buf[j] + carry;
            carry = d > 9;
            buf[j] = carry ? d - 10 : d;
        }
        m = j;
    }
    for (i = 0; i < 38; i++) {
        if (buf[i]) {
            break;
        }
    }
    for (j = i; j < 39; j++) {
        buf[j] += '0';
    }
    return buf + i;
}

(Aber anscheinend sind 128-Bit-Divisionen/-Module nicht so teuer, wie ich dachte. Bei einem Phenom 9600 mit GCC 4.7 und Clang 3.1 bei -O2 scheint dies eine 2x-3x-Geschwindigkeit zu haben als bei der OP-Methode.

5
ephemient

Ich denke, Ihre print_uint128-Funktion ist äußerst komplex.

Ist das nicht einfacher zu schreiben und auszuführen?

void print_uint128(uint128_t n)
{
    if (n == 0) {
      return;
    }

    print_uint128(n/10);
    putchar(n%10+0x30);
}
3
abelenky

Basierend auf der Antwort von sebastian ist dies für signiertes int128 in g ++ und nicht threadsicher.

// g++ -Wall fact128.c && a.exe
// 35! overflows 128bits

#include <stdio.h>

char * sprintf_int128( __int128_t n ) {
    static char str[41] = { 0 };        // sign + log10(2**128) + '\0'
    char *s = str + sizeof( str ) - 1;  // start at the end
    bool neg = n < 0;
    if( neg )
        n = -n;
    do {
        *--s = "0123456789"[n % 10];    // save last digit
        n /= 10;                // drop it
    } while ( n );
    if( neg )
        *--s = '-';
    return s;
}

__int128_t factorial( __int128_t i ) {
    return i < 2 ? i : i * factorial( i - 1 );
}

int main(  ) {
    for( int i = 0; i < 35; i++ )
        printf( "fact(%d)=%s\n", i, sprintf_int128( factorial( i ) ) );
    return 0;
} 
3
mosh

Sie können dieses einfache Makro verwenden:

typedef __int128_t int128 ;
typedef __uint128_t uint128 ;

uint128  x = (uint128) 123;

printf("__int128 max  %016"PRIx64"%016"PRIx64"\n",(uint64)(x>>64),(uint64)x);
3
user2107435

Als ich die Antwort von Abelenky oben ablegte, kam ich dazu. 

void uint128_to_str_iter(uint128_t n, char *out,int firstiter){
    static int offset=0;
    if (firstiter){
        offset=0;
    }
    if (n == 0) {
      return;
    }
    uint128_to_str_iter(n/10,out,0);
    out[offset++]=n%10+0x30;
}

char* uint128_to_str(uint128_t n){
    char *out=calloc(sizeof(char),40);
    uint128_to_str_iter(n, out, 1);
    return out;
}

Was scheint wie beabsichtigt zu funktionieren.

1
Perkins

wie man __uint128_t nummer mit gcc druckt?
Gibt es PRIu128, die sich ähnlich wie PRIu64 verhält:

Nein. Statt in decimal zu drucken, drucken Sie als String. 

Die Größe des erforderlichen Zeichenfolgepuffers reicht gerade aus, um den Job mit dem Wert von x auszuführen. 

typedef signed __int128 int128_t;
typedef unsigned __int128 uint128_t;

// Return pointer to the end
static char *uint128toa_helper(char *dest, uint128_t x) {
  if (x >= 10) {
    dest = uint128toa_helper(dest, x / 10);
  }
  *dest = (char) (x % 10 + '0');
  return ++dest;
}

char *int128toa(char *dest, int128_t x) {
  if (x < 0) {
    *dest = '-';
    *uint128toa_helper(dest + 1, (uint128_t) (-1 - x) + 1) = '\0';
  } else {
    *uint128toa_helper(dest, (uint128_t) x) = '\0';
  }
  return dest;
}

char *uint128toa(char *dest, uint128_t x) {
  *uint128toa_helper(dest, x) = '\0';
  return dest;
}

Prüfung. Puffergröße im ungünstigsten Fall: 41.

int main(void) {
  char buf[41];
  puts("1234567890123456789012345678901234567890");
  puts(uint128toa(buf, 0));
  puts(uint128toa(buf, 1));
  puts(uint128toa(buf, (uint128_t) -1));
  int128_t mx = ((uint128_t) -1) / 2;
  puts(int128toa(buf, -mx - 1));
  puts(int128toa(buf, -mx));
  puts(int128toa(buf, -1));
  puts(int128toa(buf, 0));
  puts(int128toa(buf, 1));
  puts(int128toa(buf, mx));
  return 0;
}

Ausgabe

1234567890123456789012345678901234567890
0
1
340282366920938463463374607431768211455
-170141183460469231731687303715884105728
-170141183460469231731687303715884105727
-1
0
1
170141183460469231731687303715884105727
0
chux

Dies ist für C++, aber ich lasse es hier, da ich keine C++ - Version dieser Frage für ohne Vorzeichen 128-Bit-Ints gefunden habe.

Hier ist eine einfache, lesbare Methode, um eine uint128 in eine Base-10-Zeichenfolge umzuwandeln (mit der Sie dann drucken oder tun können, was Sie möchten):

std::string toString(__uint128_t num) {
    std::string str;
    do {
        int digit = num % 10;
        str = std::to_string(digit) + str;
        num = (num - digit) / 10;
    } while (num != 0);
    return str;
}

Bei Bedarf können wir es um ein Vielfaches beschleunigen, indem wir die Ziffern in größeren Blöcken anstatt einzeln abrufen. Es erfordert jedoch, dass wir jeden Block auf führende Nullen überprüfen, die verloren gegangen sind, und sie wieder hinzufügen:

std::string toString(__uint128_t num) {
    auto tenPow19 = 10000000000000000000;
    std::string str;
    do {
        uint64_t digits = num % tenPow19;
        auto digitsStr = std::to_string(digits);
        auto leading0s = (digits != num) ? std::string(19 - digitsStr.length(), '0') : "";
        str = leading0s + digitsStr + str;
        num = (num - digits) / tenPow19;
    } while (num != 0);
    return str;
}
0
Gumby The Green

C++ - Variante. Sie können es als Vorlage verwenden, um eine spezielle C-Version der Funktion abzuleiten:

template< typename I >
void print_uint(I value)
{
    static_assert(std::is_unsigned< I >::value, "!");
    if (value == 0) {
        putchar_unlocked('0');
        return;
    }
    I rev = value;
    I count = 0;
    while ((rev % 10) == 0) {
        ++count;
        rev /= 10;
    }
    rev = 0;
    while (value != 0) {
        rev = (rev * 10) + (value % 10);
        value /= 10;
    }
    while (rev != 0) {
        putchar_unlocked('0' + (rev % 10));
        rev /= 10;
    }
    while (0 != count) {
        --count;
        putchar_unlocked('0');
    }
}
0
Orient

Hier ist eine modifizierte Version von Lefflers Antwort, die von 0 bis UINT128_MAX unterstützt

/*      UINT64_MAX 18446744073709551615ULL */
#define P10_UINT64 10000000000000000000ULL /* 19 zeroes */
#define E10_UINT64 19

#define STRINGIZER(x) # x
#define TO_STRING(x) STRINGIZER(x)

int print_uint128_decimal(__uint128_t big) {
  size_t rc = 0;
  size_t i = 0;
  if (big >> 64) {
    char buf[40];
    while (big / P10_UINT64) {
      rc += sprintf(buf + E10_UINT64 * i, "%." TO_STRING(E10_UINT64) PRIu64, (uint64_t)(big % P10_UINT64));
      ++i;
      big /= P10_UINT64;
    }
    rc += printf("%" PRIu64, (uint64_t)big);
    while (i--) {
      fwrite(buf + E10_UINT64 * i, sizeof(char), E10_UINT64, stdout);
    }
  } else {
    rc += printf("%" PRIu64, (uint64_t)big);
  }
  return rc;
}

Und versuche es:

print_uint128_decimal(-1); // Assuming -1's complement being 0xFFFFF...
0
bumfo