Calcolo della lunghezza di Base64?


155

Dopo aver letto il wiki di base64 ...

Sto cercando di capire come funziona la formula:

Data una stringa con lunghezza di n, sarà la lunghezza base64inserisci qui la descrizione dell'immagine

Che è : 4*Math.Ceiling(((double)s.Length/3)))

So già che la lunghezza di base64 deve essere %4==0per consentire al decodificatore di sapere qual era la lunghezza del testo originale.

Il numero massimo di padding per una sequenza può essere =o ==.

wiki: il numero di byte di output per byte di input è circa 4/3 (sovraccarico del 33%)

Domanda:

In che modo le informazioni sopra riportate si adattano alla lunghezza dell'output inserisci qui la descrizione dell'immagine?

Risposte:


211

Ogni carattere viene utilizzato per rappresentare 6 bit ( log2(64) = 6).

Pertanto vengono utilizzati 4 caratteri per rappresentare 4 * 6 = 24 bits = 3 bytes.

Quindi hai bisogno di 4*(n/3)caratteri per rappresentare nbyte, e questo deve essere arrotondato per eccesso a un multiplo di 4.

Il numero di caratteri di riempimento non utilizzati risultanti dall'arrotondamento per un multiplo di 4 sarà ovviamente 0, 1, 2 o 3.


dove arriva l'imbottitura?
Royi Namir,

1
Considera se hai un byte di input. Ciò produrrà quattro caratteri di output. Ma per codificare l'input sono necessari solo due caratteri di output. Quindi due personaggi saranno riempiti.
David Schwartz,

2
La lunghezza dell'output viene sempre arrotondata per eccesso a un multiplo di 4, quindi 1, 2 o 3 byte di input => 4 caratteri; 4, 5 o 6 byte di input => 8 caratteri; 7, 8 o 9 byte di input => 12 caratteri.
Paolo R,

5
Ho spiegato tutto questo nella risposta sopra: (i) ogni carattere di output rappresenta 6 bit di input, (ii) 4 caratteri di output quindi rappresentano 4 * 6 = 24 bit , (iii) 24 bit è 3 byte , (iv) 3 byte di input pertanto risulta in 4 caratteri di output, (v) il rapporto tra caratteri di output e byte di input è quindi 4 / 3.
Paul R

2
@ techie_28: lo faccio 27308 caratteri per 20 * 1024 byte, ma non ho ancora preso il caffè stamattina.
Paul R,

61

4 * n / 3 dà una lunghezza non imbottita.

E arrotondare al multiplo più vicino di 4 per il riempimento, e poiché 4 è una potenza di 2 è possibile utilizzare operazioni logiche bit a bit.

((4 * n / 3) + 3) & ~3

1
Hai ragione! -> 4 * n / 3 fornisce una lunghezza non imbottita! le risposte sopra non sono corrette. -> ((4 * n / 3) + 3) & ~ 3 restituisce il risultato giusto
Cadburry

Non funziona come input per l'API CryptBinaryToStringA di Windows.
TarmoPikaro,

1
per precisarlo per le persone che usano shell:$(( ((4 * n / 3) + 3) & ~3 ))
Starfry

1
4 * n / 3fallisce già a n = 1, un byte viene codificato usando due caratteri e il risultato è chiaramente un carattere.
Maarten Bodewes,

1
@Crog Come è scritto se n = 1, otterrai 4/3 = 1 usando numeri interi. Come hai indicato, il risultato atteso è 2, non 1.
Maarten Bodewes,

25

Per riferimento, la formula della lunghezza dell'encoder Base64 è la seguente:

Formula della lunghezza dell'encoder Base64

Come hai detto, un codificatore Base64 dato nbyte di dati produrrà una stringa di 4n/3caratteri Base64. Detto in altro modo, ogni 3 byte di dati comporteranno 4 caratteri Base64. MODIFICA : un commento sottolinea correttamente che la mia grafica precedente non teneva conto del riempimento; la formula corretta è Ceiling(4n/3) .

L'articolo di Wikipedia mostra esattamente come la stringa ASCII Man codificata nella stringa Base64 TWFunel suo esempio. La stringa di input ha una dimensione di 3 byte o 24 bit, quindi la formula prevede correttamente che l'output sarà lungo 4 byte (o 32 bit):TWFu . Il processo codifica ogni 6 bit di dati in uno dei 64 caratteri Base64, quindi l'input a 24 bit diviso per 6 risulta in 4 caratteri Base64.

In un commento chiedi quale sia la dimensione della codifica 123456 sarebbe . Tenendo presente che ogni carattere di quella stringa ha dimensioni di 1 byte o 8 bit (assumendo la codifica ASCII / UTF8), stiamo codificando 6 byte o 48 bit di dati. Secondo l'equazione, prevediamo che la lunghezza dell'output sia (6 bytes / 3 bytes) * 4 characters = 8 characters.

Mettere 123456in un codificatore Base64 crea MTIzNDU2, che è lungo 8 caratteri, proprio come ci aspettavamo.


5
Usando questa formula, tieni presente che non fornisce la lunghezza imbottita. Quindi puoi avere una lunghezza maggiore.
Spilarix,

Per calcolare i byte decodificati previsti dal testo base64, utilizzo la formula floor((3 * (length - padding)) / 4). Dai un'occhiata al seguente riassunto .
Kurt Vangraefschepe,

13

Interi

Generalmente non vogliamo usare i doppi perché non vogliamo usare le operazioni in virgola mobile, gli errori di arrotondamento ecc. Non sono necessari.

Per questo è una buona idea ricordare come eseguire la divisione del soffitto: ceil(x / y)in doppio può essere scritto come(x + y - 1) / y (evitando numeri negativi, ma attenzione all'overflow).

Leggibile

Se vai per la leggibilità puoi ovviamente anche programmarlo in questo modo (esempio in Java, per C potresti usare le macro, ovviamente):

public static int ceilDiv(int x, int y) {
    return (x + y - 1) / y;
}

public static int paddedBase64(int n) {
    int blocks = ceilDiv(n, 3);
    return blocks * 4;
}

public static int unpaddedBase64(int n) {
    int bits = 8 * n;
    return ceilDiv(bits, 6);
}

// test only
public static void main(String[] args) {
    for (int n = 0; n < 21; n++) {
        System.out.println("Base 64 padded: " + paddedBase64(n));
        System.out.println("Base 64 unpadded: " + unpaddedBase64(n));
    }
}

inline

Imbottito

Sappiamo che abbiamo bisogno di blocchi di 4 caratteri alla volta per ogni 3 byte (o meno). Quindi la formula diventa (per x = n e y = 3):

blocks = (bytes + 3 - 1) / 3
chars = blocks * 4

o combinato:

chars = ((bytes + 3 - 1) / 3) * 4

il tuo compilatore ottimizzerà il 3 - 1, quindi lascialo così per mantenere la leggibilità.

non imbottita

Meno comune è la variante non imbottita, per questo ricordiamo che ognuno ha bisogno di un carattere per ogni 6 bit, arrotondato per eccesso:

bits = bytes * 8
chars = (bits + 6 - 1) / 6

o combinato:

chars = (bytes * 8 + 6 - 1) / 6

possiamo comunque dividere ancora per due (se vogliamo):

chars = (bytes * 4 + 3 - 1) / 3

Illeggibile

Nel caso in cui non ti fidi del tuo compilatore per fare le ottimizzazioni finali per te (o se vuoi confondere i tuoi colleghi):

Imbottito

((n + 2) / 3) << 2

non imbottita

((n << 2) | 2) / 3

Quindi ci sono due modi logici di calcolo e non abbiamo bisogno di rami, bit-op o modulo-op - a meno che non lo vogliamo davvero.

Appunti:

  • Ovviamente potrebbe essere necessario aggiungere 1 ai calcoli per includere un byte di terminazione nullo.
  • Per Mime potrebbe essere necessario occuparsi dei possibili caratteri di fine riga e simili (cercare altre risposte per questo).

5

Penso che le risposte fornite manchino il punto della domanda originale, ovvero quanto spazio deve essere allocato per adattarsi alla codifica base64 per una data stringa binaria di lunghezza n byte.

La risposta è (floor(n / 3) + 1) * 4 + 1

Ciò include il riempimento e un carattere null terminante. Potrebbe non essere necessaria la chiamata al piano se si esegue l'aritmetica dei numeri interi.

Compreso il riempimento, una stringa base64 richiede quattro byte per ogni blocco di tre byte della stringa originale, inclusi eventuali blocchi parziali. Uno o due byte in più alla fine della stringa verranno comunque convertiti in quattro byte nella stringa base64 quando viene aggiunto il riempimento. A meno che tu non abbia un uso molto specifico, è meglio aggiungere l'imbottitura, di solito un carattere uguale. Ho aggiunto un byte aggiuntivo per un carattere null in C, perché le stringhe ASCII senza questo sono un po 'pericolose e dovresti portare la lunghezza della stringa separatamente.


5
La tua formula è sbagliata. Considera n = 3, il risultato atteso (senza riempimento nullo) è 4, ma la tua formula restituisce 8.
CodesInChaos

5
Penso anche che includere il terminatore null sia sciocco, soprattutto perché qui stiamo parlando di .net.
Codici A Caos il

Funziona correttamente in Windows, usando CryptBinaryToStringA. Il mio voto per questo.
TarmoPikaro,

5

Ecco una funzione per calcolare la dimensione originale di un file Base 64 codificato come stringa in KB:

private Double calcBase64SizeInKBytes(String base64String) {
    Double result = -1.0;
    if(StringUtils.isNotEmpty(base64String)) {
        Integer padding = 0;
        if(base64String.endsWith("==")) {
            padding = 2;
        }
        else {
            if (base64String.endsWith("=")) padding = 1;
        }
        result = (Math.ceil(base64String.length() / 4) * 3 ) - padding;
    }
    return result / 1000;
}

3

Mentre tutti gli altri stanno discutendo di formule algebriche, preferirei semplicemente usare BASE64 stesso per dirmi:

$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately."| wc -c

525

$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately." | base64 | wc -c

710

Quindi sembra che la formula di 3 byte rappresentata da 4 caratteri base64 sia corretta.


1
Ho qualcosa contro i calcoli che richiedono molta memoria e tempo della CPU mentre i calcoli possono essere eseguiti in 1 ns e uno o due registri.
Maarten Bodewes,

Quindi, quando stai cercando di gestire quantità sconosciute di dati binari, in che modo aiuta?
UKMonkey,

La domanda riguarda le formule, che aiutano a calcolare la dimensione dell'output senza fare la base64 stessa. Sebbene questa risposta sia utile in alcune situazioni, non aiuta con questa domanda.
Alejandro,

3

(Nel tentativo di dare una derivazione concisa ma completa.)

Ogni byte di input ha 8 bit, quindi per n byte di input otteniamo:

n × 8 bit di input

Ogni 6 bit è un byte di output, quindi:

ceil ( n × 8/6 ) =  byte ( n × 4/3 ) byte di output

Questo è senza imbottitura.

Con il riempimento, arrotondiamo a un massimo di quattro byte di output:

ceil ( ceil ( n × 4/3 ) / 4) × 4 =  ceil ( n × 4/3/4 ) × 4 =  ceil ( n / 3) × 4 byte di output

Vedi Divisioni annidate (Wikipedia) per la prima equivalenza.

Usando l'aritmetica dei numeri interi, ceil ( n / m ) può essere calcolato come ( n + m - 1) div m , quindi otteniamo:

( n * 4 + 2) div 3 senza imbottitura

( n + 2) div 3 * 4 con imbottitura

Per illustrazione:

 n   with padding    (n + 2) div 3 * 4    without padding   (n * 4 + 2) div 3 
------------------------------------------------------------------------------
 0                           0                                      0
 1   AA==                    4            AA                        2
 2   AAA=                    4            AAA                       3
 3   AAAA                    4            AAAA                      4
 4   AAAAAA==                8            AAAAAA                    6
 5   AAAAAAA=                8            AAAAAAA                   7
 6   AAAAAAAA                8            AAAAAAAA                  8
 7   AAAAAAAAAA==           12            AAAAAAAAAA               10
 8   AAAAAAAAAAA=           12            AAAAAAAAAAA              11
 9   AAAAAAAAAAAA           12            AAAAAAAAAAAA             12
10   AAAAAAAAAAAAAA==       16            AAAAAAAAAAAAAA           14
11   AAAAAAAAAAAAAAA=       16            AAAAAAAAAAAAAAA          15
12   AAAAAAAAAAAAAAAA       16            AAAAAAAAAAAAAAAA         16

Infine, nel caso della codifica MIME Base64, sono necessari due byte aggiuntivi (CR LF) ogni 76 byte di output, arrotondati per eccesso o per difetto a seconda che sia necessaria una nuova riga di terminazione.


Grazie per l'analisi dettagliata
P Satish Patro,

2

Mi sembra che la formula giusta dovrebbe essere:

n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)

Il riempimento zero di Ascii non viene preso in considerazione - non funziona in Windows. (CryptBinaryToStringA)
TarmoPikaro

1

Credo che questa sia una risposta esatta se n% 3 non zero, no?

    (n + 3-n%3)
4 * ---------
       3

Versione Mathematica:

SizeB64[n_] := If[Mod[n, 3] == 0, 4 n/3, 4 (n + 3 - Mod[n, 3])/3]

Divertiti

GI


1

Implementazione semplice in javascript

function sizeOfBase64String(base64String) {
    if (!base64String) return 0;
    const padding = (base64String.match(/(=*)$/) || [])[1].length;
    return 4 * Math.ceil((base64String.length / 3)) - padding;
}

1

Per tutte le persone che parlano in C, dai un'occhiata a queste due macro:

// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 encoding operation
#define B64ENCODE_OUT_SAFESIZE(x) ((((x) + 3 - 1)/3) * 4 + 1) 

// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 decoding operation
#define B64DECODE_OUT_SAFESIZE(x) (((x)*3)/4) 

Tratto da qui .


1

Non vedo la formula semplificata in altre risposte. La logica è coperta ma volevo un modulo di base per il mio uso incorporato:

  Unpadded = ((4 * n) + 2) / 3

  Padded = 4 * ((n + 2) / 3)

NOTA: quando si calcola il conteggio non imbottito, arrotondiamo per eccesso la divisione intera, cioè aggiungiamo Divisore-1 che è +2 in questo caso


0

In Windows - volevo stimare la dimensione del buffer di dimensioni mime64, ma tutte le formule di calcolo precise non funzionavano per me - finalmente ho finito con una formula approssimativa come questa:

Dimensione allocazione stringa Mine64 (approssimativa) = ((((4 * ((dimensione del buffer binario) + 1)) / 3) + 1)

Quindi l'ultimo +1 - è usato per ascii-zero - l'ultimo carattere deve essere allocato per memorizzare la fine zero - ma perché la "dimensione del buffer binario" è + 1 - sospetto che ci sia un carattere di terminazione mime64? O potrebbe essere questo è un problema di allineamento.


0

Se c'è qualcuno interessato a realizzare la soluzione @Pedro Silva in JS, ho appena portato questa stessa soluzione:

const getBase64Size = (base64) => {
  let padding = base64.length
    ? getBase64Padding(base64)
    : 0
  return ((Math.ceil(base64.length / 4) * 3 ) - padding) / 1000
}

const getBase64Padding = (base64) => {
  return endsWith(base64, '==')
    ? 2
    : 1
}

const endsWith = (str, end) => {
  let charsFromEnd = end.length
  let extractedEnd = str.slice(-charsFromEnd)
  return extractedEnd === end
}
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.