Module:Roman

-- This module implements. requirestrict

local p = {}

-- This function implements the template. local function overline(s) return mw.ustring.format( ' %s ', s ) end

-- Gets the Roman numerals for a given numeral table. Returns both the string of -- numerals and the value of the number after it is finished being processed. local function getLetters(num, t)   local ret = {} for _, v in ipairs(t) do       local val, letter = unpack(v) while num >= val do           num = num - val table.insert(ret, letter) end end

return table.concat(ret), num end

-- The main control flow of the module. local function _main(args) -- Get input and exit displaying nothing if the input is empty. if args[1] == nil then return end local num = tonumber(args[1]) if not num or num < 0 or num == math.huge then error('Invalid number ' .. args[1], 2) elseif num == 0 then return 'N'   end

-- Return a message for numbers too big to be expressed in Roman numerals. if num >= 5000000 then return args[2] or 'N/A' end

local ret = '' -- Find the Roman numerals for the large part of numbers. -- 23 April 2016 - tweaked to >= 4000 to accept big Roman 'IV' -- The if statement is not strictly necessary, but makes the algorithm -- more efficient for smaller numbers. if num >= 4000 then local bigRomans = { { 1000000, 'M' }, { 900000, 'CM' }, { 500000, 'D' }, { 400000, 'CD' }, { 100000, 'C' }, { 90000, 'XC' }, {  50000, 'L' }, {  40000, 'XL' }, {  10000, 'X' }, {  9000, 'IX' }, {   5000, 'V' }, {   4000, 'IV' }, }       local bigLetters bigLetters, num = getLetters(num, bigRomans) ret = overline(bigLetters) end

-- Find the Roman numerals for numbers less than the big Roman threshold. local smallRomans = { { 1000, 'M' }, { 900, 'CM' }, { 500, 'D' }, { 400, 'CD' }, { 100, 'C' }, { 90, 'XC' }, {  50, 'L' }, {  40, 'XL' }, {  10, 'X' }, {  9, 'IX' }, {   5, 'V' }, {   4, 'IV' }, {   1, 'I' } }   local smallLetters = getLetters( num, smallRomans ) ret = ret .. smallLetters

if args.fraction == 'yes' then -- Find the Roman numerals for the fractional parts of numbers. -- If num is not a whole number, add half of 1/1728 (the smallest unit) to equate to rounding. -- Ensure we're not less than the smallest unit or larger than 1 - smallest unit -- to avoid getting two "half" symbols or no symbols at all num = num - math.floor(num) if num ~= 0 then num = math.max(1.1/1728, math.min(1727.1/1728, num + 1/3456)) end local fractionalRomans = { { 1/2, 'S' }, { 5/12, ":•:" }, { 1/3, "::" }, { 1/4, ":•" }, { 1/6, ":" }, { 1/12, '•' },           { 1/24, 'Є' }, { 1/36, 'ƧƧ' }, { 1/48, 'Ɔ' }, { 1/72, 'Ƨ' }, { 1/144, ' Ƨ ' }, { 1/288, '℈' }, { 1/1728, '»' },       }        local fractionalLetters = getLetters(num, fractionalRomans) ret = ret .. fractionalLetters end

return ret 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 local success, result = pcall(mw.ext.ParserFunctions.expr, args[1]) if success then args[1] = result end -- else, pass to _main routine and try to let Lua's tonumber handle it   end return _main(args) end

return p