Module:Roman Numeral

Documentation for this module may be created at Module:Roman Numeral/doc

local function split(source, sep)
    local result = { [1] = '', [2] = '', [3] = '' }
    local count = 1
    for c in source:gmatch('.') do
        if (c == sep) then
            count = count + 1
        end
        if (result[count] == nil or result[count] == '') then
            result[count] = c
        else
            result[count] = result[count] .. c
        end
        if (c == sep) then
            count = count + 1
        end
    end
    return result
end

local function join(intermediate, toJoin)

    local result = ""
    local len = #toJoin
    local count = 1
    for i, v in ipairs(toJoin) do
        if (count >= len) then
            result = result .. v
        else
            result = result .. v .. intermediate
        end
        count = count + 1
    end

    return result
end

local function createStringWithNZeroes(n)
    local result = ""
    for i=1, n do
        result = result .. '0'
    end
    return result
end

-- function overline(s)
--     return mw.ustring.format( '<span style="text-decoration:overline;">%s</span>', s )
-- end

local function overline(s)
    local str = ''
    for c in s:gmatch('.') do
        str = str .. c --.. '\u{305}'
    end
    return str
end

local function truncate(f, n)
	local s = tostring(f)
	local Split = split(s, '.')
    local decimal = Split[3] .. createStringWithNZeroes(n)
	return tonumber(join('.',{ Split[1], decimal:sub(1, n) }))
end

function table.contains(Table, element)
    for _, value in pairs(Table) do
        if value == element then
            return true
        end
    end
    return false
end

function table.any(Table, TableToCompare)
    for _, value in pairs(Table) do
        for _, v in pairs(TableToCompare) do
            if value == v then
                return true
            end
        end
    end
    return false
end

function table.anyString(StringToCompare, Table)
    for _, value in pairs(Table) do
        for c in StringToCompare:gmatch('.') do
            if value == c then
                return true
            end
        end
    end
    return false
end


function table.slice(tbl, first, last, step)
    local sliced = {}

    for i = first or 1, last or #tbl, step or 1 do
        sliced[#sliced+1] = tbl[i]
    end

    return sliced
end

local p = {}

local function _main(args)
	local tensThou = {"", "ↂ", "ↂↂ", "ↂↂↂ", "ↂↇ", "ↇ", "ↇↂ", "ↇↂↂ", "ↇↂↂↂ", "ↂↈ"}
	local thou = {"", "M", "MM", "MMM", "Mↁ", "ↁ", "ↁM", "ↁMM", "ↁMMM", "Mↂ"}
	local huns = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}
	local hunThou = {"", "ↈ", "ↈↈ", "ↈↈↈ", "ↈ" .. overline(huns[6]), overline(huns[6]), overline(huns[6]) .. "ↈ", overline(huns[6]) .. "ↈↈ", overline(huns[6]) .. "ↈↈ", "ↂ" .. overline(thou[2])}
    local tensMil = {"", overline(tensThou[2]), overline(tensThou[2]) .. overline(tensThou[2]), overline(tensThou[2]) .. overline(tensThou[2]) .. overline(tensThou[2]), overline(tensThou[2]) .. overline(tensThou[6]), overline(tensThou[6]), overline(tensThou[6]) .. overline(tensThou[2]), overline(tensThou[6]) .. overline(tensThou[2]) .. overline(tensThou[2]), overline(tensThou[6]) .. overline(tensThou[2]) .. overline(tensThou[2]) .. overline(tensThou[2]), overline(tensThou[2]) .. overline(hunThou[2])}
	local mils = {"", overline(thou[2]), overline(thou[2]) .. overline(thou[2]), overline(thou[2]) .. overline(thou[2]) .. overline(thou[2]), overline(thou[2]) .. overline(thou[6]), overline(thou[6]), overline(thou[6]) .. overline(thou[2]), overline(thou[6]) .. overline(thou[2]) .. overline(thou[2]), overline(thou[6]) .. overline(thou[2]) .. overline(thou[2]) .. overline(thou[2]), overline(thou[2]) .. overline(tensThou[2])}
    local tens = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}
    local ones = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
    local twelths = {"", "·", ":", "∴", "∷", "⁙", "S", "S·", "S:", "S∴", "S∷", "S⁙"}
    local twentyFourths = {"", "Σ", "·", "·Σ", ":", ":Σ", "∴", "∴Σ", "∷", "∷Σ", "⁙", "ΣS", "S", "SΣ", "S·", "S·Σ", "S:", "S:Σ", "S:Σ", "S∴", "S∴Σ", "S∷", "S∷Σ", "S⁙", "S⁙Σ"}
    local thirds = {truncate(1/3, 4), truncate(1/6, 4), truncate(2/3, 4), truncate(5/6, 4)}

	if args[1] == nil then return end
    local num = tonumber(args[1])
    if not num or num < 0 or num == math.huge then
    	return {'',''}
    elseif num == 0 then
        return {'N','\n0 is represented by N'}
    end

    local roman = ''
    local explanation = "\n"
    local count = 0

    while num >= 100000000 do
    	roman = roman .. overline(hunThou[2])
        num = num - 100000000
        count = count + 1
    end

    if (count > 0) then
        explanation = explanation .. tostring(count*100000000) .. " is represented by " .. roman .."\n"
    end

    roman = roman .. tensMil[1+math.floor(math.floor(tonumber(num))/10000000)]
    if (num/10000000 >= 1) then
        explanation = explanation .. tostring(math.floor(tonumber(num / 10000000))*10000000) ..  " is represented by ".. tostring(tensMil[1+math.floor(math.floor(tonumber(num))/10000000)]) .."\n"
    end
    num = num % 10000000

    roman = roman .. mils[1+math.floor(math.floor(tonumber(num))/1000000)]
    if (num / 1000000 >= 1) then
        explanation = explanation .. tostring(math.floor(math.floor(tonumber(num / 1000000))*1000000)) ..  " is represented by " .. mils[1+math.floor(math.floor(tonumber(num)) / 1000000)] .."\n"
    end
    num = num % 1000000

    roman = roman .. hunThou[1+math.floor(tonumber(math.floor(tonumber(num))/100000))]
    if (num / 100000 >= 1) then
        explanation = explanation .. tostring(math.floor(tonumber(math.floor(tonumber(num))/100000))*100000) ..  " is represented by " .. hunThou[1+math.floor(tonumber(math.floor(tonumber(num))/100000))] .."\n"
    end
    num = num % 100000

    roman = roman .. tensThou[1+math.floor(tonumber(math.floor(tonumber(num))/10000))]
    if (num / 10000 >= 1) then
        explanation = explanation .. tostring(math.floor(tonumber(math.floor(tonumber(num))/10000))*10000) ..  " is represented by " .. tensThou[1+math.floor(tonumber(math.floor(tonumber(num))/10000))] .."\n"
    end
    num = num % 10000

    roman = roman .. thou[1+math.floor(tonumber(math.floor(tonumber(num))/1000))]
    if (num / 1000 >= 1) then
        explanation = explanation .. tostring(math.floor(tonumber(math.floor(tonumber(num))/1000))*1000) ..  " is represented by " .. thou[1+math.floor(tonumber(math.floor(tonumber(num))/1000))] .."\n"
    end
    num = num % 1000

    roman = roman .. huns[1+math.floor(math.floor(tonumber(num))/100)]
    if (num / 100 >= 1) then
        explanation = explanation .. tostring(math.floor(math.floor(tonumber(num / 100))*100)) ..  " is represented by " .. huns[1+math.floor(math.floor(tonumber(num)) / 100)] .."\n"
    end
    num = num % 100

    roman = roman .. tens[1+math.floor(math.floor(tonumber(num))/10)]
    if (num / 10 >= 1) then
        explanation = explanation .. tostring(math.floor(math.floor(tonumber(num / 10))*10)) ..  " is represented by " .. tens[1+math.floor(math.floor(tonumber(num)) / 10)] .."\n"
    end
    num = num % 10

    roman = roman .. ones[1+math.floor(math.floor(tonumber(num)))]
    if (num >= 1) then
        explanation = explanation .. math.floor(tostring(math.floor(tonumber(num)))) ..  " is represented by " .. ones[1+math.floor(math.floor(tonumber(num)))] .."\n"
    end
    num = num % 1
    if not table.contains(thirds, truncate(num, 4)) then
        roman = roman .. twelths[1+math.floor(tonumber(num/(1/12)))]
        if table.anyString(roman, table.slice(twelths, 2)) then
            explanation = explanation .. math.floor(tonumber(num / (1/12)))*(1/12) .. " is represented by ".. twelths[1+math.floor(tonumber(num/(1/12)))] .."\n"
        end
        num = num % (1/12)

        roman = roman .. twentyFourths[1+math.floor(tonumber(num/(1/24)))]
        if table.anyString(roman, table.slice(twentyFourths, 2)) and (math.floor(tonumber(num/(1/24)))*(1/24) ~= 0) then
            explanation = explanation .. math.floor(tonumber(num/(1/24)))*(1/24) .. " is represented by ".. twentyFourths[1+math.floor(tonumber(num/(1/24)))] .."\n"
        end
        num = num % (1/24)
    else
        local number1 = truncate(num, 4)
        if number1 == thirds[1] then
            roman = roman .. twelths[6]
            if table.anyString(roman, table.slice(twelths, 2)) then
                explanation = explanation .. tostring(num) .. "is represented by " .. twelths[6] .. "\n"
            end
        elseif number1 == thirds[2] then
            roman = roman .. twelths[4]
            if table.anyString(roman, table.slice(twelths, 2)) then
                explanation = explanation .. tostring(num) .. "is represented by " .. twelths[4] .. "\n"
            end
        elseif number1 == thirds[3] then
            roman = roman .. twelths[9]
            if table.anyString(roman, table.slice(twelths, 2)) then
                explanation =  explanation .. tostring(num) .. "is represented by " .. twelths[9] .. "\n"
            end
        elseif number1 == thirds[4] then
            roman = roman .. twelths[11]
            if table.anyString(roman, table.slice(twelths, 2)) then
                explanation = explanation .. tostring(num) .. "is represented by " .. twelths[11] .. "\n"
            end
        end
    end

    return {roman, explanation}

end

function p.main(frame)
    -- If called via #invoke, use the args passed into the invoking
    -- template, or the args passed to #invoke if any exist. Otherwise
    -- assume args are being passed directly in from the debug console
    -- or from another Lua module.
    local origArgs
    if frame == mw.getCurrentFrame() then
        origArgs = frame:getParent().args
        for k, v in pairs(frame.args) do
            origArgs = frame.args
            break
        end
    else
        origArgs = frame
    end
    -- Trim whitespace and remove blank arguments.
    local args = {}
    for k, v in pairs(origArgs) do
        if type( v ) == 'string' then
            v = mw.text.trim(v)
        end
        if v ~= '' then
            args[k] = v
        end
    end

    -- exit if not given anything
    if args == nil or args == {} then return end
    -- Given mathematical expression, simplify to a number
    if type(args[1]) == 'string' then
        args[1] = mw.ext.ParserFunctions.expr(args[1])
    end

    local numExplanation = _main(args)
    if numExplanation == nil or numExplanation == {} then return end
    local res = ''
    if args.explanation == "yes" then
        for _, v in pairs(numExplanation) do
            res = res .. v
        end
        return res
    else
        return numExplanation[1]
    end
end



return p