web-dev-qa-db-fra.com

comment imprimer le numéro __uint128_t en utilisant gcc?

Y at-il PRIu128 qui se comporte semblable à PRIu64 à partir de <inttypes.h>:

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

Ou convertir manuellement chiffre par chiffre:

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

est la seule option?

Notez que uint128_t est ma propre typedef pour __uint128_t.

45
jfs

Non, la bibliothèque n'accepte pas l'impression de ces types. Ils ne sont même pas des types entiers étendus au sens de la norme C.

Votre idée pour commencer l’impression à partir du dos est bonne, mais vous pourriez utiliser des morceaux beaucoup plus gros. Dans certains tests pour P99, j'ai une telle fonction qui utilise 

uint64_t const d19 = UINT64_C(10000000000000000000);

comme la plus grande puissance de 10 qui s'inscrit dans un uint64_t.

Décimales, ces gros nombres deviennent illisibles très rapidement. Une autre option, plus simple, consiste à les imprimer au format hexadécimal. Ensuite, vous pouvez faire quelque chose comme

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

pour obtenir la moitié inférieure et puis fondamentalement la même chose avec

  uint64_t high = (x >> 64);

pour la moitié supérieure.

14
Jens Gustedt

Le manuel GCC 4.7.1 dit:

6.8 entiers 128 bits

En tant qu'extension, le type scalaire entier __int128 est pris en charge pour les cibles ayant un entier mode suffisamment large pour contenir 128 bits. Écrivez simplement __int128 pour un entier signé de 128 bits, ou unsigned __int128 pour un entier non signé de 128 bits. Il n’existe aucun support dans GCC pour exprimer une constante entière de type __int128 pour les cibles ayant long long entier avec moins que [sic] Largeur de 128 bits.

Fait intéressant, bien que cela ne mentionne pas __uint128_t, ce type est accepté, même avec des avertissements rigoureux:

#include <stdio.h>

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

Compilation:

$ 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]
$

(Ceci est avec un GCC 4.7.1 compilé à la maison sur Mac OS X 10.7.4.)

Changez la constante en 0x12345678900987654321 et le compilateur dit:

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

Donc, ce n'est pas facile de manipuler ces créatures. Les sorties avec la constante décimale et les constantes hexadécimales sont les suivantes:

ab54a98cdc6770b1
5678900987654321

Pour imprimer en décimal, votre meilleur choix est de voir si la valeur est supérieure à UINT64_MAX; si c'est le cas, divisez par la plus grande puissance de 10 inférieure à UINT64_MAX, imprimez ce nombre (et vous devrez peut-être répéter le processus une seconde fois), puis imprimez au résidu modulo la plus grande puissance de 10 inférieure à UINT64_MAX, en rappelant de compléter avec des zéros en tête.

Cela conduit à quelque chose comme:

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

Le résultat de ceci est:

24197857200151252746022455506638221840
38 digits
321944928255972408260334335944939549199
39 digits

Nous pouvons vérifier en utilisant 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
$

Clairement, pour hex, le processus est plus simple; vous pouvez déplacer, masquer et imprimer en deux opérations seulement. Pour octal, puisque 64 n’est pas un multiple de 3, vous devez suivre des étapes analogues à l’opération décimale.

L’interface print_u128_u() n’est pas idéale, mais elle renvoie au moins le nombre de caractères imprimés, exactement comme le permet printf(). L'adaptation du code pour formater le résultat dans une mémoire tampon de chaîne n'est pas un exercice de programmation totalement trivial, mais pas terriblement difficile.

28
Jonathan Leffler

Je n'ai pas de solution intégrée, mais la division/module coûte cher. Vous pouvez convertir des valeurs binaires en valeurs décimales avec des décalages seulement.

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

(Mais apparemment, la division/module 128 bits ne sont pas aussi coûteux que je le pensais. Sur un Phenom 9600 avec GCC 4.7 et Clang 3.1 sous -O2, cela semble fonctionner 2x plus lentement que la méthode de OP.)

5
ephemient

Je pense que votre fonction print_uint128 est terriblement complexe.

N'est-ce pas plus simple à écrire et à exécuter?

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

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

Basé sur la réponse de sebastian, c'est pour int128 signé en g ++, pas thread-safe.

// 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

Vous pouvez utiliser cette macro simple:

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

En partant de la réponse de abelenky ci-dessus, je suis arrivé à cela. 

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

Ce qui semble fonctionner comme prévu.

1
Perkins

comment imprimer le numéro __uint128_t en utilisant gcc?
Existe-t-il un PRIu128 similaire à PRIu64 à partir de:

N ° Au lieu d'imprimer dans decimal , imprimer sur une chaîne. 

La taille du tampon de chaîne nécessaire est juste suffisante pour effectuer le travail avec la valeur 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;
}

Tester. Taille de la mémoire tampon dans le pire des cas: 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;
}

Sortie

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

Variante C++. Vous pouvez l'utiliser comme modèle pour dériver la version C spécialisée de la fonction:

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

Voici une version modifiée de la réponse de Leffler qui prend en charge de 0 à UINT128_MAX

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

Et essayez ceci:

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