Un buon schema per rappresentare numeri interi da 0 a infinito, supponendo che tu abbia una memoria binaria lineare infinita?


10

Vorrei che uno schema rappresentasse numeri interi che iniziano con 0, senza alcun limite (presupponendo l'accesso a memoria lineare infinita).

Ecco uno schema che può rappresentare numeri da 0 a 255:

Utilizzare il primo byte della memoria (indirizzo 0) per memorizzare il numero intero.

Supponiamo ora di voler rappresentare numeri maggiori di 255. Naturalmente, potrei usare più di 1 byte per rappresentare l'intero, ma finché sarà un numero fisso, alla fine ci sarà un intero così grande che non può essere rappresentato da lo schema originale.

Ecco un altro schema che dovrebbe essere in grado di svolgere l'attività, ma è probabilmente tutt'altro che efficiente.

Basta usare una sorta di byte "end of number" univoco e utilizzare tutti i byte precedenti per rappresentare il numero. Ovviamente, questo byte di "fine del numero" non può essere usato da nessuna parte nella rappresentazione numerica, ma ciò può essere ottenuto usando un sistema di numerazione base-255 (anziché base-256).

Tuttavia, è lento e probabilmente inefficiente. Voglio averne uno migliore che funzioni meglio con valori bassi e scala bene.

In sostanza, è un sistema UUID. Voglio vedere se è possibile creare un sistema UUID dalle prestazioni veloci che può teoricamente scalare per essere utilizzato per anni, migliaia di anni, milioni di anni, senza dover essere riprogettato.


1
Vuoi qualcosa che possa ridimensionarsi all'infinito (come nella tua apertura) o per milioni di anni (come nella tua chiusura)? I due requisiti sono (ovviamente) completamente diversi. Il complemento a due su una macchina a 64 bit si ridimensionerà per milioni di anni.
user16764

1
@ user16764, vuoi dire una singola variabile intera a 64 bit? Di certo non funzionerà: se 6 milioni di persone consumano 1 milione di UUID al secondo, durerà a malapena più di un mese.
Dmitri Shuralyov,

1
E quanto tempo impiegherebbe una macchina a 128 bit?
user16764

2
Le idee in RFC 2550 , che fornisce una rappresentazione ASCII ordinata lessicograficamente per numeri interi arbitrariamente grandi, possono essere adattabili a questo. Alla fine si scompone in un segmento unario che codifica la lunghezza di un segmento base-26 che codifica la lunghezza di un segmento base-10 - le ultime due basi hanno più a che fare con la rappresentazione ASCII di qualsiasi cosa fondamentale per lo schema.
Casuale 832

1
Supponendo di generare sequenzialmente numeri a 128 bit: se aumentassimo la capacità di calcolo di tutti i computer dando a ogni essere umano un computer petaflop, sarebbero necessari 9 milioni di anni prima che questi numeri si esaurissero. Se d'altra parte ogni umano generasse casualmente 600 milioni di numeri a 128 bit, c'è una probabilità del 50% di generare 1 duplicato. Ti va abbastanza bene? ( en.wikipedia.org/wiki/Universally_unique_identifier ) In caso contrario, l'utilizzo di 256 bit moltiplica entrambe queste cifre per 2 ^ 128 = 3.4 * 10 ^ 38, che è più del quadrato dell'età dell'universo in secondi.
Alex ten Brink,

Risposte:


13

Un approccio che ho usato: contare il numero di 1 bit iniziali, diciamo n. La dimensione del numero è quindi 2 ^ n byte (inclusi i 1 bit iniziali). Prendi i bit dopo il primo 0 bit come numero intero e aggiungi il valore massimo (più uno) che può essere rappresentato da un numero usando questa codifica in 2 ^ (n-1) byte.

Così,

                  0 = 0b00000000
                   ...
                127 = 0b01111111
                128 = 0b1000000000000000
                   ...
              16511 = 0b1011111111111111
              16512 = 0b11000000000000000000000000000000
                   ...
          536887423 = 0b11011111111111111111111111111111
          536887424 = 0b1110000000000000000000000000000000000000000000000000000000000000
                   ...
1152921505143734399 = 0b1110111111111111111111111111111111111111111111111111111111111111
1152921505143734400 = 0b111100000000000000000000000000000000000000000000 ...

Questo schema consente di rappresentare qualsiasi valore non negativo esattamente in un modo.

(Equivalentemente, ha utilizzato il numero di 0 bit iniziali.)


1
È stato difficile per me capire quale risposta contrassegnare come accettata, perché penso che molti di loro siano molto istruttivi e validi. Ma penso che questo sia il migliore per la domanda che ho posto (forse non quello sottostante che avevo in mente, che è più difficile da esprimere).
Dmitri Shuralyov,

2
Ho scritto un articolo più approfondito con esempi di implementazione e considerazioni di progettazione.
Retracile

10

C'è molta teoria basata su ciò che stai cercando di fare. Dai un'occhiata alla pagina wiki sui codici universali : esiste un elenco piuttosto esaustivo di metodi di codifica di numeri interi (alcuni dei quali vengono effettivamente utilizzati nella pratica).

Nella compressione dei dati, un codice universale per numeri interi è un codice prefisso che mappa gli interi positivi su parole binarie

Oppure potresti semplicemente usare i primi 8 byte per memorizzare la lunghezza del numero in alcune unità (molto probabilmente byte) e quindi inserire i byte di dati. Sarebbe molto facile da implementare, ma piuttosto inefficiente per piccoli numeri. E sarai in grado di codificare numeri interi abbastanza a lungo da riempire tutte le unità di dati disponibili per l'umanità :)


Grazie per quello, è molto interessante. Volevo contrassegnarlo come risposta accettata, ma è arrivato al 2 ° posto. Questa è un'ottima risposta dal punto di vista teorico, IMO.
Dmitri Shuralyov,

4

Che ne dite di lasciare che il numero di 1 iniziali più il primo 0 sia la dimensione (sizeSize) della dimensione del numero (numSize) in bit. NumSize è un numero binario che indica la dimensione della rappresentazione numerica in byte, inclusi i bit di dimensione. I bit rimanenti sono il numero (num) in binario. Per uno schema intero positivo, ecco alcuni esempi di numeri di esempio:

Number              sizeSize  numSize    num
63:                 0 (1)     1 (1)      111111
1048575:            10 (2)    11 (3)     1111 11111111 11111111
1125899906842623:   110 (3)   111 (7)    11 11111111 11111111 11111111 11111111 11111111 11111111
5.19.. e+33:        1110 (4)  1111 (15)  11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

4

Che ne dici di questo: un byte per la lunghezza, quindi n byte per il numero (prima il byte meno significativo). Ripeti lunghezza + numero finché la lunghezza precedente era 255.

Ciò consente numeri arbitrariamente grandi, ma è comunque facile da gestire e non spreca troppa memoria.


fNek: non esiste un limite superiore. Ad esempio, se sono necessari 513 byte per il numero, la sequenza di byte è [255, b0, ..., b255,255, b256, ..., b511,2, b512, b513]
user281377

Scusate. Dovrebbe imparare a leggere più attentamente.
fNek

3

Perché non usare solo 7 bit per ogni byte e usare l'ottavo bit per indicare se c'è un altro byte da seguire? Quindi 1-127 sarebbe in un byte, 128 sarebbe rappresentato da 0x80 0x01, ecc.


1
Questo schema codifica solo 128 valori in ogni 8 bit, che in realtà è meno efficiente in termini di spazio rispetto al secondo schema di codifica proposto dall'interrogatore, in cui 255 valori sono codificati in ogni 8 bit. Entrambi gli schemi soffrono del fatto che è necessario leggere l'intero numero per scoprire quanta memoria è necessaria per memorizzarlo.
Mark Booth,

3
Quindi devi scannerizzare il numero due volte per farne una copia, e allora? Se posso aspettare un numero infinitamente grande, posso aspettarlo due volte.
Russell Borogove,

Anche se non l'ho specificato con molta attenzione, sto cercando una soluzione che funzioni nel modo più efficiente possibile (anziché una soluzione che soddisfi semplicemente i requisiti; ho già descritto una potenziale risposta inefficiente nella mia domanda).
Dmitri Shuralyov,

3

I sistemi UUID si basano su una potenza di calcolo finita (ma grande) in un universo finito (ma grande). Il numero di UUID è grande anche se confrontato con cose assurdamente grandi come il numero di particelle nell'universo. Il numero di UUID, con qualsiasi numero di bit fissi, è tuttavia piccolo rispetto all'infinito.

Il problema con l'utilizzo di 0xFFFF per rappresentare il flag di fine numero è che rende meno efficiente la codifica dei numeri quando i numeri sono grandi. Tuttavia, sembra che il tuo schema UUID peggiori ulteriormente questo problema. Invece di uno su 256 byte saltati, ora hai perso l'intero spazio UUID. L'efficienza del calcolo / riconoscimento (anziché dello spazio) dipende molto dal tuo computer teorico (che, presumo tu abbia se stai parlando di infinito). Per una TM con un nastro e un controller a stati finiti, qualsiasi schema UUID è impossibile da scalare in modo efficiente (in pratica, il lemma di pompaggio ti impedisce di spostarti in modo efficiente oltre un marker di estremità a lunghezza di bit fissa). Se non si assume un controller a stato finito, questo potrebbe non essere applicabile, ma è necessario pensare a dove vanno i bit nel processo di decodifica / riconoscimento.

Se desideri solo una migliore efficienza rispetto a 1 su 256 byte, puoi utilizzare la lunghezza in bit di 1 che avresti utilizzato per il tuo schema UUID. Questo è 1 su 2 ^ lunghezza in bit di inefficienza.

Si noti che esistono altri schemi di codifica. La codifica dei byte con delimitatori sembra essere la più semplice da implementare.


2

Suggerirei di avere una matrice di byte (o ints o long) e un campo di lunghezza che indica quanto è lungo il numero.

Questo è approssimativamente l'approccio utilizzato da BigInteger di Java . Lo spazio di indirizzi possibile da questo è enorme - abbastanza facilmente da dare un UUID diverso a ogni singolo atomo nell'universo :-)

A meno che tu non abbia una buona ragione per fare diversamente, suggerirei di usare direttamente BigInteger (o il suo equivalente in altre lingue). Non è necessario reinventare la ruota dei numeri grandi ....


Non è possibile codificare la lunghezza dell'array quando il numero di campi può essere infinito.
Slawek,

Concordo sul fatto che utilizzare una soluzione esistente (in particolare una che è stata sottoposta a controllo professionale) per un dato problema, quando possibile, è preferito. Grazie.
Dmitri Shuralyov,

@Slawek: vero, ma per il caso d'uso che l'OP sta descrivendo (cioè UUID), un BigInteger è effettivamente infinito. Non puoi comunque codificare informazioni infinite in qualsiasi computer con memoria di dimensioni limitate, quindi BigInteger è buono come qualsiasi altra cosa tu possa ottenere.
Mikera,

2

Prima di tutto, grazie a tutti coloro che hanno contribuito in modo eccellente alla mia domanda relativamente vaga e astratta.

Vorrei contribuire con una potenziale risposta a cui ho pensato dopo aver pensato ad altre risposte. Non è una risposta diretta alla domanda posta, ma è rilevante.

Come alcune persone hanno sottolineato, l'uso di un numero intero di 64/128/256 bit offre già uno spazio molto ampio per gli UUID. Ovviamente non è infinito, ma ...

Forse potrebbe essere una buona idea usare solo una dimensione fissa int (diciamo 64-bit per iniziare) fino a quando 64-bit non è abbastanza (o vicino ad esso). Quindi, supponendo che tu abbia tale accesso a tutte le precedenti istanze degli UUID, aggiornali tutti a 128 bit e considera quello come un intero di dimensioni fisse.

Se il sistema consente tali pause / interruzioni del servizio e poiché tali operazioni di "ricostruzione" dovrebbero avvenire abbastanza raramente, forse i vantaggi (un sistema molto semplice, veloce e facile da implementare) compenseranno gli svantaggi (dovendo ricostruire tutti gli interi precedentemente allocati con una nuova dimensione di bit intero).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.