How to encode and decode any string into Base‑36 in C

1 Answer

0 votes
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

/*
    Base‑36 encoding/decoding in C
    ----------------------------------------
    Base‑36 digits: 0–9, A–Z

    Encoding:
      - Convert string → big integer (base‑256)
      - Convert big integer → base‑36

    Decoding:
      - Convert base‑36 → big integer
      - Convert big integer → original bytes

    We store the big integer as an array of 32‑bit chunks (little‑endian).
*/

static const char BASE36_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

/* Convert Base‑36 character → numeric value */
static int base36_value(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    return c - 'A' + 10;
}

/*
    encode_to_base36:
    -----------------
    Converts a byte string into Base‑36 text.

    Uses manual big‑integer arithmetic:
      number[j] holds a 32‑bit chunk of the integer.
*/
char *encode_to_base36(const char *input) {
    size_t len = strlen(input);

    /* Big integer buffer */
    size_t cap = len + 4;
    uint32_t *number = calloc(cap, sizeof(uint32_t));
    size_t nsize = 1;

    /* Step 1: Convert string → big integer (base‑256) */
    for (size_t i = 0; i < len; i++) {
        uint32_t carry = (unsigned char)input[i];

        for (size_t j = 0; j < nsize; j++) {
            uint64_t value = (uint64_t)number[j] * 256 + carry;
            number[j] = (uint32_t)(value & 0xFFFFFFFFu);
            carry = (uint32_t)(value >> 32);
        }

        if (carry > 0)
            number[nsize++] = carry;
    }

    /* Step 2: Convert big integer → Base‑36 */
    char *encoded = malloc(len * 3 + 32);
    size_t epos = 0;

    while (!(nsize == 1 && number[0] == 0)) {
        uint32_t remainder = 0;

        /* Divide big integer by 36 */
        for (size_t j = nsize; j-- > 0;) {
            uint64_t value = ((uint64_t)remainder << 32) | number[j];
            number[j] = (uint32_t)(value / 36);
            remainder = (uint32_t)(value % 36);
        }

        /* Remove leading zero chunks */
        while (nsize > 1 && number[nsize - 1] == 0)
            nsize--;

        encoded[epos++] = BASE36_DIGITS[remainder];
    }

    /* Reverse the Base‑36 output */
    for (size_t i = 0; i < epos / 2; i++) {
        char tmp = encoded[i];
        encoded[i] = encoded[epos - 1 - i];
        encoded[epos - 1 - i] = tmp;
    }

    encoded[epos] = '\0';
    free(number);
    
    return encoded;
}

/*
    decode_from_base36:
    -------------------
    Converts Base‑36 text back into the original string.

    Steps:
      1. Convert Base‑36 → big integer
      2. Convert big integer → bytes (base‑256)
*/
char *decode_from_base36(const char *encoded) {
    size_t len = strlen(encoded);

    /* Big integer buffer */
    uint32_t *number = calloc(len + 4, sizeof(uint32_t));
    size_t nsize = 1;

    /* Step 1: Base‑36 → big integer */
    for (size_t i = 0; i < len; i++) {
        int digit = base36_value(encoded[i]);
        uint32_t carry = digit;

        for (size_t j = 0; j < nsize; j++) {
            uint64_t value = (uint64_t)number[j] * 36 + carry;
            number[j] = (uint32_t)(value & 0xFFFFFFFFu);
            carry = (uint32_t)(value >> 32);
        }

        if (carry > 0)
            number[nsize++] = carry;
    }

    /* Step 2: big integer → original bytes */
    char *output = malloc(len + 1);
    size_t opos = 0;

    while (!(nsize == 1 && number[0] == 0)) {
        uint32_t remainder = 0;

        /* Divide big integer by 256 */
        for (size_t j = nsize; j-- > 0;) {
            uint64_t value = ((uint64_t)remainder << 32) | number[j];
            number[j] = (uint32_t)(value / 256);
            remainder = (uint32_t)(value % 256);
        }

        while (nsize > 1 && number[nsize - 1] == 0)
            nsize--;

        output[opos++] = (char)remainder;
    }

    /* Reverse the byte output */
    for (size_t i = 0; i < opos / 2; i++) {
        char tmp = output[i];
        output[i] = output[opos - 1 - i];
        output[opos - 1 - i] = tmp;
    }

    output[opos] = '\0';
    free(number);
    
    return output;
}

int main(void) {
    const char *text = "Hello Universe!";
    char *encoded = encode_to_base36(text);
    char *decoded = decode_from_base36(encoded);

    printf("Original: %s\n", text);
    printf("Base-36 encoded: %s\n", encoded);
    printf("Decoded: %s\n", decoded);

    free(encoded);
    free(decoded);
}


/*
run:

Original: Hello Universe!
Base-36 encoded: LP4N024HJ1YVBVD84Y8TYQP
Decoded: Hello Universe!

*/

 



answered Jul 7 by avibootz
edited Jul 7 by avibootz
...