2011-11-05 11 views
13

Sto cercando di convertire un intero senza segno a 128 bit memorizzato come una matrice di 4 unsigned int alla rappresentazione della stringa decimale in C:Come convertire un intero a 128 bit in una stringa ascii decimale in C?

unsigned int src[] = { 0x12345678, 0x90abcdef, 0xfedcba90, 0x8765421 }; 
printf("%s", some_func(src)); // gives "53072739890371098123344" 

(Gli esempi di ingresso e uscita sopra sono completamente inventati; ho nessuna idea di cosa produrrebbe quell'input.)

Se stavo andando in esadecimale, binario o ottale, questa sarebbe una semplice questione di maschere e bit shift per rimuovere i caratteri meno significativi. Tuttavia, mi sembra che ho bisogno di fare la divisione base 10. Sfortunatamente, non riesco a ricordare come farlo attraverso più int e il sistema che sto usando non supporta tipi di dati maggiori di 32-bit, quindi non è possibile usare un tipo a 128-bit. Usando una lingua diversa è anche fuori, e preferirei evitare una grande libreria di numeri solo per questa operazione.

+0

Se non vuoi una libreria bignum, dovrai implementare la divisione lunga da solo. Funziona come l'algoritmo penna-e-carta, solo più facile perché è binario quindi non devi fare così tante ipotesi. Scoprirai che hai bisogno di sottrazione e spostamento. Sei sicuro di non voler usare una libreria di bignum? Stai per implementare uno piuttosto completo te stesso. –

+4

Il decimale è per l'uomo. Tendono a perdere interesse dopo la settima cifra. Qual è esattamente il punto di questo? –

+0

Come deve * essere * stampato sopra? Come "0x1234567890abcdef ..."? Come decimale? – AusCBloke

risposta

8

divisione non è necessaria:

#include <string.h> 
#include <stdio.h> 

typedef unsigned long uint32; 

/* N[0] - contains least significant bits, N[3] - most significant */ 
char* Bin128ToDec(const uint32 N[4]) 
{ 
    // log10(x) = log2(x)/log2(10) ~= log2(x)/3.322 
    static char s[128/3 + 1 + 1]; 
    uint32 n[4]; 
    char* p = s; 
    int i; 

    memset(s, '0', sizeof(s) - 1); 
    s[sizeof(s) - 1] = '\0'; 

    memcpy(n, N, sizeof(n)); 

    for (i = 0; i < 128; i++) 
    { 
    int j, carry; 

    carry = (n[3] >= 0x80000000); 
    // Shift n[] left, doubling it 
    n[3] = ((n[3] << 1) & 0xFFFFFFFF) + (n[2] >= 0x80000000); 
    n[2] = ((n[2] << 1) & 0xFFFFFFFF) + (n[1] >= 0x80000000); 
    n[1] = ((n[1] << 1) & 0xFFFFFFFF) + (n[0] >= 0x80000000); 
    n[0] = ((n[0] << 1) & 0xFFFFFFFF); 

    // Add s[] to itself in decimal, doubling it 
    for (j = sizeof(s) - 2; j >= 0; j--) 
    { 
     s[j] += s[j] - '0' + carry; 

     carry = (s[j] > '9'); 

     if (carry) 
     { 
     s[j] -= 10; 
     } 
    } 
    } 

    while ((p[0] == '0') && (p < &s[sizeof(s) - 2])) 
    { 
    p++; 
    } 

    return p; 
} 

int main(void) 
{ 
    static const uint32 testData[][4] = 
    { 
    { 0, 0, 0, 0 }, 
    { 1048576, 0, 0, 0 }, 
    { 0xFFFFFFFF, 0, 0, 0 }, 
    { 0, 1, 0, 0 }, 
    { 0x12345678, 0x90abcdef, 0xfedcba90, 0x8765421 } 
    }; 
    printf("%s\n", Bin128ToDec(testData[0])); 
    printf("%s\n", Bin128ToDec(testData[1])); 
    printf("%s\n", Bin128ToDec(testData[2])); 
    printf("%s\n", Bin128ToDec(testData[3])); 
    printf("%s\n", Bin128ToDec(testData[4])); 
    return 0; 
} 

uscita:

0 
1048576 
4294967295 
4294967296 
11248221411398543556294285637029484152 
+0

'unsigned long' è 8-byes su un sistema a 64 bit. Vedo che si mascherano i valori di n mentre si cambia e si usa 'sizeof', che è ottimo, ma in generale se si chiama qualcosa 'uint32', dovrebbe essere lungo 4 byte. Suggerirei di cambiare il typedef in 'unsigned int', o di usare' long long' e di cambiare typedef in 'uint64' (o semplicemente usando' stdint.h'). –

+0

Mi piace questo - tutte le operazioni veloci, l'unico svantaggio che posso vedere è che è O (n^2) per il numero di bit. Perché "& 0xFFFFFFFF"? Mi chiedo di cambiare "s [j] + = s [j] - '0' + carry;" a "s [j] = (s [j] << 1) | carry;" e quindi facendo un passaggio sulla stringa alla fine con "s [j] + = '0'; – Silverhalide

+1

@BobbyPowers:' unsigned int' non è garantito per essere lungo almeno 32 bit. 'unsigned long'. il sistema è a 64 bit o non è irrilevante e oltre lo standard C. –

0

In realtà non è necessario implementare la divisione lunga. È necessario implementare la moltiplicazione con una potenza di due e aggiunta. Hai quattro uint_32. Prima converti ciascuno di essi in una stringa. Moltiplicali per (2^32)^3, (2^32)^2, (2^32)^1 e (2^32)^0 rispettivamente, quindi aggiungili insieme. Non è necessario eseguire la conversione di base, è sufficiente gestire i quattro pezzi. Ovviamente dovrai assicurarti che le stringhe possano gestire un numero fino a UINT_32_MAX*(2^32)^3.

+0

+1 Una libreria bignum di base 10 personalizzata è una buona idea, ma "la moltiplicazione per potenza di due" non è particolarmente facile nella base 10 L'OP dovrà fare la moltiplicazione generale. –

+0

Ad ogni modo, bisogna implementare la moltiplicazione di "stringhe". Cioè, tratta le stringhe come cifre decimali e la moltiplicazione – valdo

2

Un approccio lento ma semplice consiste semplicemente nella stampa di cifre dal più significativo al meno significativo utilizzando la sottrazione. Fondamentalmente è necessaria una funzione per verificare se x >= y e un altro per il calcolo x -= y quando questo è il caso. Quindi puoi iniziare a contare quante volte puoi sottrarre 10^38 (e questa sarà la cifra più significativa), quindi quante volte puoi sottrarre 10^37 ... giù a quante volte puoi sottrarre 1.

la seguente è una piena attuazione di questo approccio:

#include <stdio.h> 

typedef unsigned ui128[4]; 

int ge128(ui128 a, ui128 b) 
{ 
    int i = 3; 
    while (i >= 0 && a[i] == b[i]) 
     --i; 
    return i < 0 ? 1 : a[i] >= b[i]; 
} 

void sub128(ui128 a, ui128 b) 
{ 
    int i = 0; 
    int borrow = 0; 
    while (i < 4) 
    { 
     int next_borrow = (borrow && a[i] <= b[i]) || (!borrow && a[i] < b[i]); 
     a[i] -= b[i] + borrow; 
     borrow = next_borrow; 
     i += 1; 
    } 
} 

ui128 deci128[] = {{1u,0u,0u,0u}, 
        {10u,0u,0u,0u}, 
        {100u,0u,0u,0u}, 
        {1000u,0u,0u,0u}, 
        {10000u,0u,0u,0u}, 
        {100000u,0u,0u,0u}, 
        {1000000u,0u,0u,0u}, 
        {10000000u,0u,0u,0u}, 
        {100000000u,0u,0u,0u}, 
        {1000000000u,0u,0u,0u}, 
        {1410065408u,2u,0u,0u}, 
        {1215752192u,23u,0u,0u}, 
        {3567587328u,232u,0u,0u}, 
        {1316134912u,2328u,0u,0u}, 
        {276447232u,23283u,0u,0u}, 
        {2764472320u,232830u,0u,0u}, 
        {1874919424u,2328306u,0u,0u}, 
        {1569325056u,23283064u,0u,0u}, 
        {2808348672u,232830643u,0u,0u}, 
        {2313682944u,2328306436u,0u,0u}, 
        {1661992960u,1808227885u,5u,0u}, 
        {3735027712u,902409669u,54u,0u}, 
        {2990538752u,434162106u,542u,0u}, 
        {4135583744u,46653770u,5421u,0u}, 
        {2701131776u,466537709u,54210u,0u}, 
        {1241513984u,370409800u,542101u,0u}, 
        {3825205248u,3704098002u,5421010u,0u}, 
        {3892314112u,2681241660u,54210108u,0u}, 
        {268435456u,1042612833u,542101086u,0u}, 
        {2684354560u,1836193738u,1126043566u,1u}, 
        {1073741824u,1182068202u,2670501072u,12u}, 
        {2147483648u,3230747430u,935206946u,126u}, 
        {0u,2242703233u,762134875u,1262u}, 
        {0u,952195850u,3326381459u,12621u}, 
        {0u,932023908u,3199043520u,126217u}, 
        {0u,730304488u,1925664130u,1262177u}, 
        {0u,3008077584u,2076772117u,12621774u}, 
        {0u,16004768u,3587851993u,126217744u}, 
        {0u,160047680u,1518781562u,1262177448u}}; 

void print128(ui128 x) 
{ 
    int i = 38; 
    int z = 0; 
    while (i >= 0) 
    { 
     int c = 0; 
     while (ge128(x, deci128[i])) 
     { 
      c++; sub128(x, deci128[i]); 
     } 
     if (i==0 || z || c > 0) 
     { 
      z = 1; putchar('0' + c); 
     } 
     --i; 
    } 
} 

int main(int argc, const char *argv[]) 
{ 
    ui128 test = { 0x12345678, 0x90abcdef, 0xfedcba90, 0x8765421 }; 
    print128(test); 
    return 0; 
} 

Quel numero nel testo problema in decimale diventa

11248221411398543556294285637029484152 

e Python è d'accordo questo è il valore corretto (questo naturalmente non lo fa significa che il codice è corretto !!! ;-))

+0

'unsigned' non è garantita per contenere 32 bit, 16 bit è il minimo richiesto per esso. Usa invece 'unsigned long', che contiene almeno 32 bit per lo standard. –

+0

Ho pensato che il problema fosse molto specifico, non generale. Dal testo non firmato sono 32 bit (l'OP sta usando 4 valori senza segno per rappresentare un numero di 128 bit). – 6502

+0

Probabilmente non sarebbe troppo difficile modificarlo per fare una divisione di 1.000.000.000 (che è la più grande potenza di 10 rappresentabile in un valore di 32 bit) anziché di 10. Ciò ridurrebbe le operazioni 9 volte. – Silverhalide

4

Semplice base di divisione 2^32, stampe cifre decimali in ordine inverso, utilizza 64 bit aritmetica, complessità O(n) dove n è il numero di cifre decimali nella rappresentazione:

#include <stdio.h> 

unsigned int a [] = { 0x12345678, 0x12345678, 0x12345678, 0x12345678 }; 

/* 24197857161011715162171839636988778104 */ 

int 
main() 
{ 
    unsigned long long d, r; 

    do 
    { 
     r = a [0]; 

     d = r/10; 
     r = ((r - d * 10) << 32) + a [1]; 
     a [0] = d; 

     d = r/10; 
     r = ((r - d * 10) << 32) + a [2]; 
     a [1] = d; 

     d = r/10; 
     r = ((r - d * 10) << 32) + a [3]; 
     a [2] = d; 

     d = r/10; 
     r = r - d * 10; 
     a [3] = d; 

     printf ("%d\n", (unsigned int) r); 
    } 
    while (a[0] || a[1] || a[2] || a[3]); 

    return 0; 
} 

MODIFICA: corretto il ciclo in modo che venga visualizzato un numero 0 se l'array a contiene solo zeri. Inoltre, la matrice viene letta da sinistra a destra, una [0] è la più significativa, una [3] è una cifra meno significativa.

+0

Non stampa nulla se a [0] = a [1] = a [2] = a [3] = 0. –

+0

@Alex, sì, lo so. Dovrebbe essere do {} while(); – chill

+0

Sfortunatamente, utilizza l'aritmetica a 64 bit, che non è disponibile. ... anche se immagino di poter rielaborare il problema come 8 valori a 16 bit e che mi consentirebbe di usare l'aritmetica a 32 bit dove si usa 64, ma richiedere il doppio del numero di operazioni. – Silverhalide

2

Stessa cosa, ma con 32 bit aritmetica intera:

#include <stdio.h> 

unsigned short a [] = { 
    0x0876, 0x5421, 
    0xfedc, 0xba90, 
    0x90ab, 0xcdef, 
    0x1234, 0x5678 
}; 

int 
main() 
{ 
    unsigned int d, r; 

    do 
    { 
     r = a [0]; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [1]; 
     a [0] = d; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [2]; 
     a [1] = d; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [3]; 
     a [2] = d; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [4]; 
     a [3] = d; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [5]; 
     a [4] = d; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [6]; 
     a [5] = d; 

     d = r/10; 
     r = ((r - d * 10) << 16) + a [7]; 
     a [6] = d; 

     d = r/10; 
     r = r - d * 10; 
     a [7] = d; 

     printf ("%d\n", r); 
    } 
    while (a[0] || a[1] || a[2] || a[3] || a [4] || a [5] || a[6] || a[7]); 


    return 0; 
} 
0

Supponendo di avere un facile 32 bit moltiplicazione e divisione il risultato può essere calcolata 4 cifre alla volta attuando una divisione bigint/modulo 10000 e quindi usando (s) printf per l'output di gruppi di cifre.

Questo approccio è anche banale estendere ad una maggiore precisione (o variabile) ...

#include <stdio.h> 

typedef unsigned long bigint[4]; 

void print_bigint(bigint src) 
{ 
    unsigned long int x[8]; // expanded version (16 bit per element) 
    int result[12];   // 4 digits per element 
    int done = 0;    // did we finish? 
    int i = 0;    // digit group counter 

    /* expand to 16-bit per element */ 
    x[0] = src[0] & 65535; 
    x[1] = src[0] >> 16; 
    x[2] = src[1] & 65535; 
    x[3] = src[1] >> 16; 
    x[4] = src[2] & 65535; 
    x[5] = src[2] >> 16; 
    x[6] = src[3] & 65535; 
    x[7] = src[3] >> 16; 

    while (!done) 
    { 
     done = 1; 
     { 
      unsigned long carry = 0; 
      int j; 
      for (j=7; j>=0; j--) 
      { 
       unsigned long d = (carry << 16) + x[j]; 
       x[j] = d/10000; 
       carry = d - x[j] * 10000; 
       if (x[j]) done = 0; 
      } 
      result[i++] = carry; 
     } 
    } 

    printf ("%i", result[--i]); 
    while (i > 0) 
    { 
     printf("%04i", result[--i]); 
    } 
} 

int main(int argc, const char *argv[]) 
{ 
    bigint tests[] = { { 0, 0, 0, 0 }, 
         { 0xFFFFFFFFUL, 0, 0, 0 }, 
         { 0, 1, 0, 0 }, 
         { 0x12345678UL, 0x90abcdefUL, 0xfedcba90UL, 0x8765421UL } }; 
    { 
     int i; 
     for (i=0; i<4; i++) 
     { 
      print_bigint(tests[i]); 
      printf("\n"); 
     } 
    } 
    return 0; 
} 
0

metodo @Alexey Frunze è facile ma è molto lento. Dovresti utilizzare il metodo integer a 32 bit di @ chill qui sopra. Un altro metodo facile senza alcuna moltiplicazione o divisione è double dabble. Questo potrebbe funzionare più lentamente dell'algoritmo di chill ma molto più veloce di quello di Alexey. Dopo aver eseguito, avrai un numero BCD del numero decimale compresso