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

1 Answer

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

    Encoding:
      - Convert string → bytes (UTF‑8)
      - Convert bytes → big integer (base‑256)
      - Convert big integer → Base‑36

    Decoding:
      - Convert Base‑36 → big integer
      - Convert big integer → bytes (base‑256)
      - Convert bytes → original UTF‑8 string

    We implement our own big‑integer using arrays of 32‑bit chunks.
*/

$BASE36_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

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

/* Encode any string into Base‑36 */
function encode_to_base36($input) {
    global $BASE36_DIGITS;

    $bytes = unpack("C*", $input);   // array of bytes
    $number = [0];                   // big integer (little‑endian 32‑bit chunks)

    // Convert bytes → big integer (base‑256)
    foreach ($bytes as $b) {
        $carry = $b;

        for ($i = 0; $i < count($number); $i++) {
            $value = ($number[$i] * 256) + $carry;
            $number[$i] = $value & 0xFFFFFFFF;
            $carry = $value >> 32;
        }

        if ($carry > 0) {
            $number[] = $carry;
        }
    }

    // Convert big integer → Base‑36
    $encoded = "";

    while (!(count($number) == 1 && $number[0] == 0)) {
        $remainder = 0;

        for ($i = count($number) - 1; $i >= 0; $i--) {
            $value = ($remainder << 32) | $number[$i];
            $number[$i] = intdiv($value, 36);
            $remainder = $value % 36;
        }

        while (count($number) > 1 && end($number) == 0) {
            array_pop($number);
        }

        $encoded .= $BASE36_DIGITS[$remainder];
    }

    return strrev($encoded);
}

/* Decode Base‑36 back into original string */
function decode_from_base36($encoded) {
    $encoded = strtoupper($encoded);
    $number = [0];   // big integer

    // Convert Base‑36 → big integer
    foreach (str_split($encoded) as $c) {
        $digit = base36_value($c);
        $carry = $digit;

        for ($i = 0; $i < count($number); $i++) {
            $value = ($number[$i] * 36) + $carry;
            $number[$i] = $value & 0xFFFFFFFF;
            $carry = $value >> 32;
        }

        if ($carry > 0) {
            $number[] = $carry;
        }
    }

    // Convert big integer → bytes (base‑256)
    $bytes = [];

    while (!(count($number) == 1 && $number[0] == 0)) {
        $remainder = 0;

        for ($i = count($number) - 1; $i >= 0; $i--) {
            $value = ($remainder << 32) | $number[$i];
            $number[$i] = intdiv($value, 256);
            $remainder = $value % 256;
        }

        while (count($number) > 1 && end($number) == 0) {
            array_pop($number);
        }

        $bytes[] = $remainder;
    }

    $bytes = array_reverse($bytes);
    return pack("C*", ...$bytes);
}

/* Usage */
$text = "Hello Universe!";
$encoded = encode_to_base36($text);
$decoded = decode_from_base36($encoded);

echo "Original: $text\n";
echo "Base-36 encoded: $encoded\n";
echo "Decoded: $decoded\n";


/*
run:

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

*/

 



answered 3 days ago by avibootz
edited 3 days ago by avibootz
...