Numbers to words module script

what it says in the title, might work with number-extension systems if

  1. they use metamethods
  2. tostring gives the full number and not a abbreviation
local module = {}

function numberInRange(n, mn, mx)
	return (n>=mn) and (n<=mx)
end

function splitStringEveryN(str, n)
	local result = {}
	local len = #str
	for i = 1, len, n do
		table.insert(result, string.sub(str, i, i + n - 1))
	end
	return result
end

function reverseTable(t)
	local n = #t
	for i = 1, math.floor(n / 2) do
		t[i], t[n - i + 1] = t[n - i + 1], t[i]
	end
	return t
end

function singleDigitToWord(n)
	local numbers = {"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"}
	if n == 0 then
		return "zero"
	elseif n < 0 then
		n = math.abs(n)
		return "Negative "..numbers[n]
	else
		return numbers[n]
	end
end

function doubleDigitToWord(n)
	local isNegative = (n<0)
	local n = math.abs(n)
	local check = tostring(n)
	if string.len(check) == 1 then
		return singleDigitToWord(n)
	end
	local tenCases = {"Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"}
	local otherNumbers = {"", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"}
	local FirstDigit = tonumber(string.sub(tostring(n), 1, 1))
	local SecondDigit = tonumber(string.sub(tostring(n), 2, 2))
	if isNegative then
		for i, v in ipairs(tenCases) do
			tenCases[i] = "Negative "..v
		end
		for i, v in ipairs(otherNumbers) do
			otherNumbers[i] = "Negative "..v
		end
	end
	
	if FirstDigit == 1 then
		-- special case for ten to make the rest of the code cleaner
		if SecondDigit == 0 then
			if isNegative then
				return "Negative ten"
			else
				return "ten"
			end
		else
			return tenCases[SecondDigit]
		end
	else
		if SecondDigit == 0 then
			return otherNumbers[FirstDigit]
		else
			return otherNumbers[FirstDigit].."-"..singleDigitToWord(SecondDigit)
		end
	end
end

function tripleDigitToWord(n)
	local isNegative = (n<0)
	n = math.abs(n)
	local FirstDigit = tonumber(string.sub(tostring(n), 1, 1))
	local TheRest = tonumber(string.sub(tostring(n), 2, 3))
	local finalResult = singleDigitToWord(FirstDigit).." Hundred"
	if TheRest ~= 0 then
		finalResult = finalResult.." "..doubleDigitToWord(TheRest)
	end
	if isNegative then
		finalResult = "Negative "..finalResult
	end
	return finalResult
end

function aboveTripleDigitsToWord(n, stringmode)
	-- infinitely extendable! probably also works with any system that expands numbers in lua, as long as they use metamethods
	local largeNumbers = {"", "Thousand", "Million", "Billion", "Trillion", "Quadrillion", "Quintillion", "Sextillion", "Septillion", "Octillio", "Nonillion", "Decillion", "Undecillion", "Duodecillion", "Tredecillion", "Quattuordecillion", "Quindecillion", "Sexdecillion", "Septendecillion", "Octodecillion", "Novemdecillion", "Vigintillion", "Unvigintillion"}
	local isNegative
	if stringmode then
		isNegative = (string.sub(n, 1, 1)=="-")
		if isNegative then
			n = string.sub(n, 2, string.len(n))
		end
	else
		isNegative = (n<0)
		n = tostring(math.abs(n))
	end
	local NLength = string.len(n)
	local howmanytoadd = (NLength%3)
	if howmanytoadd == 1 then
		howmanytoadd = howmanytoadd+1
	elseif howmanytoadd == 2 then
		howmanytoadd = howmanytoadd-1
	end
	-- make the amount of digits in the number divisible by 3
	for i = 1, howmanytoadd do
		n = "0"..n
	end
	NLength = string.len(n)
	local NSpilt = reverseTable(splitStringEveryN(n, 3))
	local finalTable = {}
	for i, v in ipairs(NSpilt) do
		v = tonumber(v)
		local result = belowThreeDigitsToWord(v)
		if result ~= 0 then
			if i == 1 then
				table.insert(finalTable, result)
			else
				result = result.." "..largeNumbers[i]
				table.insert(finalTable, result)
			end
		end
	end
	finalTable = reverseTable(finalTable)
	local returnal = table.concat(finalTable, " ")
	if stringmode and isNegative then
		returnal = "Negative "..returnal
	end
	return returnal
end

function module:numberToWord(n, titlecase)
	local stringmode = false
	if type(n) == "string" then
		stringmode = true
	end
	local returnal = nil
	-- math.floor but for... strings :scream:
	if stringmode then
		local decimalplace = string.find(n, ".")
		n = string.sub(n, 1, decimalplace-1)
	else
		n = math.floor(n)
	end
	local NLength = string.len(tostring(n))
	if stringmode then
		if string.sub(n, 1, 1) == "-" then
			NLength -= 1
		end
	end
	if NLength <= 3 and n ~= math.huge then
		returnal = belowThreeDigitsToWord(n)
	elseif numberInRange(NLength, 4, 67) and n ~= math.huge then
		returnal = aboveTripleDigitsToWord(n, stringmode)
	else
		return "NUMBER TOO BIG"
	end
	if not titlecase then
		returnal = string.lower(returnal)
	end
	return returnal
end
return module

Ehh… no offense, but this isn’t really descriptive on what the code actually does.
In my personal opinion’; documentation should be a top 1 priority when giving out code.

the empty strings are there to offset the tables (so 2 == twenty)
anyways here’s a example
number go in → less then 3 digits? → go to the needed converter → ten is special case because I don’t know how to 0-index a table → triple only sends through TheRest to doubledigit if its not zero (so no one hundred zero) → else → add enough zeros to make the amount divisible by 3 (because n == (tonumber(00n)) and the names of the label for large numbers go up to the hundreds → spilt up then reverse the table (because its easier to tell what’s a special case hundred and making looking up stuff in tables easier) → reverse back the table → turn it into a string → add negative if it was infact a negative number → if not title-case (ie One Hundred Eleven) then lowercase it all (better then the alternative of converting it to titlecase) → here’s your string, hot and ready

Again, no offense; that’s a very.. difficult to read documentation, additionally the documentation should be in the post itself since that’s where people look for it first and foremost.

1 Like