CustomNumbers - Convert your base 10 numbers to a custom alphabet

Quick note before we start: This is not a substitute for converting between things like base-10 to binary or other predefined bases. As in if you give it the alphabet {“0”, “1”}, then 1 will encode to 0 and 2 will encode to 1, 3 is 00 and so on. This is strictly for converting to your own custom alphabet. If you’re looking for something like converting strings to base64, I have a solution for that here which is a library I modified. And I’m sure there’s good modules for the other bases as well.

Now that that’s out of the way, here’s the asset: Roblox Asset. It’s very simple and only has 2 functions after you create the object.

Usage

Using CustomNumbers is simple, first you need to require the module:

local ServerStorage = game:GetService("ServerStorage")
local CustomNumbers = require(ServerStorage.CustomNumbers)

After you have the module, you need to make a new CustomNumbers object and give it the alphabet you want. For this example I will use an alphabet with a length of 64. This is not the same as converting a string to base64, this module ONLY works with numbers

local ServerStorage = game:GetService("ServerStorage")
local CustomNumbers = require(ServerStorage.CustomNumbers)

local cn = CustomNumbers.new({"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/" })

The alphabet is a table of characters. It can theoretically be any length you want as long as you don’t have repeating characters, it should work.

After you have this it’s super simple to turn numbers into this custom alphabet and back again using the Encode and Decode methods:

local ServerStorage = game:GetService("ServerStorage")
local CustomNumbers = require(ServerStorage.CustomNumbers)

local cn = CustomNumbers.new({"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/" })

local t = cn:Encode(1243671)
print(t) -- Prints the encoded value
print(cn:Decode(t)) -- prints the decoded value

This script produces this output:
kfprp_108461

That’s basically it. If you don’t care about why I made this or the source code then you can stop reading now.

Why?

I made this because I will be using it for my matchmaking service (which you can find here) in order to save as much space as possible when saving things to memory. The large majority of the time the encoded value is equal to or less than the length of the non encoded value if the base is higher than base 10. Because everything in memory is json encoded, all characters take the same space: 1 byte so every character saved is important.

I also made it because of topics like this. As you can see my solution to that is basically this module, but isn’t modular and has no way to decode the values again, but this module has that capability.

If you have no use for this module then simple solution: don’t use it I only decided to post it because someone else in the future might want it.

Source

Here’s the source if that’s all you want and don’t want the module itself:

local CustomNumbers = {}
CustomNumbers.__index = CustomNumbers

function CustomNumbers.new(alphabet)
	local Numbers = {}
	setmetatable(Numbers, CustomNumbers)
	Numbers.Alphabet = alphabet
	Numbers.AlphabetLength = #alphabet
	
	return Numbers
end

function CustomNumbers:Encode(x)
	local result = ""
	if x < 1 then return result end

	local quotient = x
	local remainder = -1
	
	while quotient ~= 0 do
		local dec = quotient - 1
		remainder = dec % self.AlphabetLength
		quotient = math.floor(dec / self.AlphabetLength)
		result = self.Alphabet[remainder + 1] .. result -- Compensated for lua's index 1 start
	end
	return result
end

function CustomNumbers:Decode(x)
	local sum = 0
	local index = 0

	for i = #x, 1, -1 do
		local c = x:sub(i,i)
		sum += table.find(self.Alphabet, c)*(self.AlphabetLength^index)
		index += 1
	end

	return sum
end

return CustomNumbers
6 Likes

Not always true. AFAIK, only ASCII characters take up one byte per character. Unicodes can take up more which is counterintuitive if you plan on going beyond Base93 (which was made with ASCII in mind.)
You can test this yourself; use print(string.split(char, "")) and replace char with any character you can think of. Those of ASCII should only take up one, but Unicode characters such as 爱 will take up more.
image