Wie drucke ich die __uint128_t-Nummer mit gcc?

Lesezeit: 10 Minuten

Benutzeravatar von jfs
jfs

Gibt es PRIu128 das verhält sich ähnlich PRIu64 aus <inttypes.h>:

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

Oder manuelles Konvertieren Ziffer für Ziffer:

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 Möglichkeit?

Beachten Sie, dass uint128_t ist mein eigener typedef für __uint128_t.

  • Anstatt den Druck in der Funktion auszuführen, würde ich eine Zeichenfolgendarstellung zurückgeben, sodass ich andere Dinge damit tun könnte, als sie direkt zu drucken.

    – Wug

    25. Juli 2012 um 18:34 Uhr


  • @Daniel Fischer: char str[40] = {0}; füllte das gesamte Array bereits mit Null.

    – kennytm

    25. Juli 2012 um 18:38 Uhr

  • @Wug: Ja. Normalerweise würde ich. Es ist nur ein Beispiel, um die Boiler-Plate mit herumlaufenden Puffern zu vermeiden.

    – jfs

    25. Juli 2012 um 18:38 Uhr

  • @KennyTM Oh, duh! Wie habe ich das übersehen? Danke für die Korrektur.

    – Daniel Fischer

    25. Juli 2012 um 18:40 Uhr

  • Seien Sie vorsichtig mit GCCs __uint128_t. Es verursachte uns Probleme auf einer Reihe von Plattformen, wie ARM64, ARMEL und S/390. Wir mussten es aufgeben, weil es so fehlerhaft war. Zum Beispiel berechnete GCC das Ergebnis von u = 93 - 0 - 0 - 0 (unter Verwendung der 128-Bit-Typen) als 18446744073709551615 auf ARM64.

    – jww

    22. April 2016 um 7:45 Uhr


Benutzeravatar von Jonathan Leffler
Jonathan Leffler

Das GCC 4.7.1-Handbuch sagt:

6.8 128-Bit-Ganzzahlen

Als Erweiterung der Integer-Skalar-Typ __int128 wird für Ziele mit einem Integer-Modus unterstützt, der breit genug ist, um 128 Bit aufzunehmen. Einfach schreiben __int128 für eine vorzeichenbehaftete 128-Bit-Ganzzahl oder
unsigned __int128 für eine vorzeichenlose 128-Bit-Ganzzahl. Es gibt keine Unterstützung in GCC, um eine Integer-Konstante des Typs auszudrücken __int128 für Ziele mit long long Ganzzahl mit weniger dann [sic]
128 Bit Breite.

Interessanterweise, obwohl das nicht erwähnt wird __uint128_twird dieser Typ akzeptiert, auch wenn strenge Warnungen gesetzt sind:

#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 selbst kompilierten GCC 4.7.1 auf 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 einfach, diese Kreaturen zu manipulieren. Die Ausgaben mit der Dezimalkonstante und den Hex-Konstanten sind:

ab54a98cdc6770b1
5678900987654321

Beim Dezimaldruck sollten Sie am besten 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 (und Sie müssen den Vorgang möglicherweise ein zweites Mal wiederholen) und drucken Sie dann den Rest modulo der größten Potenz von 10, die kleiner ist als UINT64_MAX, wobei daran zu denken ist, mit führenden Nullen aufzufüllen.

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 die Verwendung überprüfen bc:

$ 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 Arbeitsgängen verschieben, maskieren und drucken. Da 64 kein Vielfaches von 3 ist, müssen Sie für Oktal analoge Schritte zur Dezimaloperation durchlaufen.

Das print_u128_u() Die Schnittstelle ist nicht ideal, gibt aber zumindest die Anzahl der gedruckten Zeichen zurück printf() tut. Das Anpassen des Codes zum Formatieren des Ergebnisses in einen Zeichenfolgenpuffer ist eine nicht ganz triviale Programmierübung, aber nicht besonders schwierig.

  • __uint128_t ist nur gleichbedeutend mit unsigned __int128.

    – kennytm

    26. Juli 2012 um 14:26 Uhr

  • @KennyTM: Ja, ich kann das sehen und weiß das, aber es gibt nichts in der GCC-Dokumentation, das das sagt (was ich sehen kann).

    – Jonathan Leffler

    26. Juli 2012 um 14:32 Uhr

  • es scheint __uint128_t und __int128_t sind nur Legacy-Typen, die jetzt sind typedefed zu unsigned __int128 und __int128 beziehungsweise. Aus diesem Grund erwähnt GCC es einfach nicht. gcc.gnu.org/ml/libstdc++/2011-09/msg00068.html

    – kennytm

    26. Juli 2012 um 15:01 Uhr

  • @KennyTM: Danke für die Informationen. Ich habe den „Arbeitscode“ aktualisiert, um die bevorzugten modernen Namen anstelle der veralteten und nicht dokumentierten Alternativen zu verwenden, wobei ich feststelle, dass ältere Versionen von GCC nur die veraltete Notation und nicht die neue bevorzugte dokumentierte Notation unterstützen.

    – Jonathan Leffler

    26. Juli 2012 um 16:21 Uhr

Benutzeravatar von Jens Gustedt
Jens Gustedt

Nein, die Bibliothek unterstützt das Drucken dieser Typen nicht. 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 Stücke verwenden. In einigen Tests für P99 habe ich eine solche Funktion verwendet

uint64_t const d19 = UINT64_C(10000000000000000000);

als die größte Potenz von 10, die in ein passt uint64_t.

Als Dezimalzahl werden diese großen Zahlen sehr schnell unlesbar, also ist eine andere, einfachere Option, sie in Hex 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 gleich mit

  uint64_t high = (x >> 64);

für die obere Hälfte.

  • Warum sind sie keine erweiterten Integer-Typen im Sinne von C (N1256 6.2.5 “Typen”, nehme ich an)? Es stimmt, dass sizeof(intmax_t) gibt mir 8 und nicht 16. Warum?

    – Ciro Santilli OurBigBook.com

    19. Mai 2015 um 15:15 Uhr

  • Ah, nach C++ gefragt unter: stackoverflow.com/questions/21265462/… Schade, denn das würde es erlauben %ju Natürlich.

    – Ciro Santilli OurBigBook.com

    19. Mai 2015 um 15:54 Uhr

  • Dass UINT64_MAX das längste ist, das es in Dezimalzahlen geben könnte, nicht in Hexadezimalzahlen (was kürzer wäre, natürlich 16 Hexadezimalziffern). Übrigens, ein cleverer Weg zur Dezimalversion wäre, den Präprozessor zu verwenden, um den String durch “String-izing” von UINT64_MAX zu generieren.

    – arjunyg

    4. Dezember 2017 um 1:25 Uhr

  • Das Problem bei größeren Chunks ist, dass ihre führenden Nullen abgeschnitten werden, also müssen Sie erkennen, wann das passiert, und sie wieder hinzufügen. Aber es kann getan werden und ja, es kann viel schneller sein.

    – Gumby The Green

    3. Mai 2019 um 13:41 Uhr

Benutzeravatar von ephemient
vergänglich

Ich habe keine integrierte Lösung, aber Teilung/Modul ist teuer. Sie können binär in dezimal mit nur Verschiebungen 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-Division/Modulus nicht so teuer, wie ich dachte. Auf einem Phenom 9600 mit GCC 4.7 und Clang 3.1 at -O2dies scheint 2x-3x langsamer zu laufen als die Methode von OP.)

  • Dies erfordert immer noch Modul (j % 10) und ist wahrscheinlich viel teurer als eine einfache Schleifenkonvertierung in Dezimalzahlen, vor allem, weil 40 * 128 Mod-Operationen erforderlich sind. Sie könnten den Mod loswerden, aber er wäre wahrscheinlich immer noch langsamer, wenn Sie ihn nicht auch vektorisieren und mehrere Ziffern parallel ausführen.

    – Chris Dodd

    25. Juli 2012 um 22:28 Uhr


  • @ChrisDodd Ich habe die optimiert % entfernt, aber ein Benchmark auf meinem Rechner gibt Ihnen Recht – dieser ist immerhin langsamer, zumindest bei 128 Bit. Es verliert jedoch weniger, wenn die Zahlen größer werden … vielleicht ist diese Technik besser für Bignums.

    – vergänglich

    26. Juli 2012 um 6:58 Uhr

  • Oder sollte ich vielleicht versuchen, die Hardware-BCD-Unterstützung zu verwenden?

    – vergänglich

    26. Juli 2012 um 7:01 Uhr

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

Basierend auf Sebastians Antwort ist dies für signiertes int128 in g++, 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;
} 

Benutzeravatar von Perkins
Perkins

Ausgehend von abelenkys obiger Antwort kam ich auf diese Idee.

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 wie beabsichtigt zu funktionieren scheint.

chux – Stellt Monicas Benutzeravatar wieder her
Chux – Wiedereinsetzung von Monica

Wie drucke ich die __uint128_t-Nummer mit gcc?
Gibt es PRIu128, das sich ähnlich wie PRIu64 verhält von:

Nein. Stattdessen zum Ausdrucken Dezimalin eine Zeichenfolge drucken.

Die Größe des benötigten Zeichenfolgenpuffers reicht gerade aus, um die Aufgabe gemäß dem Wert von zu erledigen x.

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üfen. Worst-Case-Puffergröße: 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

  • Wenn Sie das Ergebnis am Anfang eines Puffers benötigen, ist es wahrscheinlich am effizientesten, am Ende eines lokalen Puffers mit fester Größe (automatische Speicherung) zu beginnen memcpy das Ergebnis in den Puffer des Aufrufers. Das ist effizienter, als die tatsächliche Rekursion zu verwenden, um nichts zu speichern, bis Sie den Stapel zurückgeben. Eine weitere Optimierung wäre die Verwendung uint64_t sobald deine Nummer passt, also (zumindest auf 64-Bit-Zielen) wirst du wahrscheinlich bekommen n%10 und n/=10 Verwenden einer multiplikativen Inversen anstelle des Aufrufens einer Hilfsfunktion für die Division mit doppelter Breite.

    – Peter Cordes

    16. Mai 2018 um 21:16 Uhr

  • @PeterCordes True – über Rekursion vs. lokalen Puffer. Wie wäre es mit a zusammengesetztes Literal für den Pufferraum als Wie man zusammengesetzte Literale verwendet fprintf() mehrere formatierte Zahlen mit beliebigen Basen??

    – chux – Wiedereinsetzung von Monica

    16. Mai 2018 um 21:24 Uhr


  • Ja, Sie könnten das als Quelle für Argumente verwenden memcpy oder fputs oder was auch immer, um alles in einer Zeile zusammenzufassen.

    – Peter Cordes

    16. Mai 2018 um 21:30 Uhr


1414450cookie-checkWie drucke ich die __uint128_t-Nummer mit gcc?

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

Privacy policy