Module:foreign numerals

This module provides conversion to and from foreign numerals to native numerical values. It's partially redundant with Module:roman numerals.

Numeral system "From" function "To" function
Roman numerals from_Roman to_Roman
Armenian numerals from_Armenian
Hebrew numerals from_Hebrew to_Hebrew
Indian numerals from_Indian to_Indian

Each function may be called either from a template (see below) or from a module. "From" functions return native numerical values (of 'number' type), while "to" functions return strings.

Example:

{{#invoke:foreign numerals|to_Roman|2764}}

gives

MMDCCLXIV

And the reverse function:

{{#invoke:foreign numerals|from_Roman|MMDCCLXIV}}

gives

2764

This module serves as a backend to the templates {{R2A}} and {{A2R}}.


local u = require("Module:string/char")

local export = {}

-- Roman numerals

local from_Roman_tab = {
	M  = 1000; CM = 900; D  = 500; CD = 400;
	C  =  100; XC =  90; L  =  50; XL =  40;
	X  =   10; IX =   9; V  =   5; IV =   4;
	I  =    1;
}

local to_Roman_tab = {
	{ 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 new_to_Roman_tab = {}

local overline = u(0x305)
local double_overline = u(0x33F)
for _, v in ipairs(to_Roman_tab) do
	local number, symbol = unpack(v)
	if number > 1 then
		table.insert(new_to_Roman_tab, { number * 1000, (symbol:gsub(".", "%1" .. overline)) })
		table.insert(new_to_Roman_tab, { number * 1000000, (symbol:gsub(".", "%1" .. double_overline)) })
	end
end

for _, v in ipairs(to_Roman_tab) do
	table.insert(new_to_Roman_tab, v)
end

to_Roman_tab = new_to_Roman_tab

function export.to_Roman(numeral)
	if type(numeral) == 'table' then
		numeral = tonumber(numeral.args[1])
	else
		-- accept strings for use by [[Module:number list/data/la]], which is invoked from [[Module:number list]]
		-- with the number in string format to allow for very large numbers
		numeral = tonumber(numeral)
	end
	
	local output = {}
	for _, item in ipairs(to_Roman_tab) do
		local limit, letter = item[1], item[2]
		while numeral >= limit do
			table.insert(output, letter)
			numeral = numeral - limit
		end
	end

	return table.concat(output)
end

function export.from_Roman(numeral)
	if type(numeral) == 'table' then
		numeral = numeral.args[1]
	end
	if tonumber(numeral) then
		return tonumber(numeral)	
	end
	
	local accum = 0
	-- shame on Lua for having no regex alternations...

	while numeral ~= "" do
		local l2, l1 = numeral:sub(1, 2), numeral:sub(1, 1)
		if from_Roman_tab[l2] then
			accum = accum + from_Roman_tab[l2]
			numeral = numeral:sub(3)
		elseif from_Roman_tab[l1] then
			accum = accum + from_Roman_tab[l1]
			numeral = numeral:sub(2)
		else
			return nil
		end
	end
	
	return accum
end

-- Armenian numerals

local from_Armenian_tab = {
	["Ա"] =    1; ["Բ"] =    2; ["Գ"] =    3; ["Դ"] =    4; ["Ե"] =    5; ["Զ"] =    6; ["Է"] =    7; ["Ը"] =    8; ["Թ"] =    9;
	["Ժ"] =   10; ["Ի"] =   20; ["Լ"] =   30; ["Խ"] =   40; ["Ծ"] =   50; ["Կ"] =   60; ["Հ"] =   70; ["Ձ"] =   80; ["Ղ"] =   90;
	["Ճ"] =  100; ["Մ"] =  200; ["Յ"] =  300; ["Ն"] =  400; ["Շ"] =  500; ["Ո"] =  600; ["Չ"] =  700; ["Պ"] =  800; ["Ջ"] =  900;
	["Ռ"] = 1000; ["Ս"] = 2000; ["Վ"] = 3000; ["Տ"] = 4000; ["Ր"] = 5000; ["Ց"] = 6000; ["Ւ"] = 7000; ["Փ"] = 8000; ["Ք"] = 9000;
}

function export.from_Armenian(numeral)
	if type(numeral) == 'table' then
		numeral = numeral.args[1]
	end
	if tonumber(numeral) then
		return tonumber(numeral)	
	end

	local accum = 0
	for cp in mw.ustring.gcodepoint(numeral) do
		local value = from_Armenian_tab[u(cp)]
		if value then
			accum = accum + value
		else
			return nil
		end
	end

	return accum	
end

-- Hebrew numerals

local from_Hebrew_tab = {
	['א'] = 1,
	['ב'] = 2,
	['ג'] = 3,
	['ד'] = 4,
	['ה'] = 5,
	['ו'] = 6,
	['ז'] = 7,
	['ח'] = 8,
	['ט'] = 9,
	['י'] = 10,
	['כ'] = 20,
	['ך'] = 20,
	['ל'] = 30,
	['מ'] = 40,
	['ם'] = 40,
	['נ'] = 50,
	['ן'] = 50,
	['ס'] = 60,
	['ע'] = 70,
	['פ'] = 80,
	['ף'] = 80,
	['צ'] = 90,
	['ץ'] = 90,
	['ק'] = 100,
	['ר'] = 200,
	['ש'] = 300,
	['ת'] = 400,
}

local to_Hebrew_ones = {[0] = '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט'}
local to_Hebrew_tens = {[0] = '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ'}
local to_Hebrew_hundreds = {[0] = '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק'}
local to_Hebrew_special = {
	[15] = 'טו',
	[16] = 'טז',
}

-- This only works for numbers such that 0 < value < 1000, because beyond that the logic gets complicated
function export.from_Hebrew(numeral)
	if type(numeral) == 'table' then
		numeral = numeral.args[1]
	end
	if tonumber(numeral) then
		return tonumber(numeral)
	end

	local value = 0
	for c in mw.ustring.gmatch(numeral, '[א-ת]') do
		value = value + (from_Hebrew_tab[c] or 0)
	end

	if value == 0 then
		return nil
	end

	return value
end

-- This only works for numbers such that 0 < value < 1000, because beyond that the logic gets complicated
function export.to_Hebrew(value, use_gershayim)
	if type(value) == 'table' then
		use_gershayim = value.args[2] ~= '' and value.args[2]
		value = value.args[1]
	end
	if type(value) ~= 'number' then
		if tonumber(value) then
			value = tonumber(value)
		else
			return nil
		end
	end

	if value <= 0 or value >= 1000 then
		return nil
	end

	local tens_and_ones = value % 100
	local hundreds = to_Hebrew_hundreds[(value - tens_and_ones) / 100]
	if to_Hebrew_special[tens_and_ones] then
		tens_and_ones = to_Hebrew_special[tens_and_ones]
	else
		local ones = tens_and_ones % 10
		local tens = (tens_and_ones - ones) / 10
		tens_and_ones = to_Hebrew_tens[tens] .. to_Hebrew_ones[ones]
	end

	local numeral = hundreds .. tens_and_ones

	if use_gershayim then
		if mw.ustring.match(numeral, '^.$') then
			numeral = numeral .. '׳'
		else
			numeral = mw.ustring.gsub(numeral, '.$', '״%0')
		end
	end

	return numeral
end

-- Indian numerals

function export.from_Indian(numeral)
	if type(numeral) == 'table' then
		value = numeral.args[1]
	else
		value = numeral
	end
	text = mw.ustring.gsub(
		tostring(value),
		'.',
		{
			['०'] = '0',
			['१'] = '1',
			['२'] = '2',
			['३'] = '3',
			['४'] = '4',
			['५'] = '5',
			['६'] = '6',
			['७'] = '7',
			['८'] = '8',
			['९'] = '9',
		}
	)
	return text
end

function export.to_Indian(numeral)
	if type(numeral) == 'table' then
		value = numeral.args[1]
	else
		value = numeral
	end
	text = mw.ustring.gsub(
		tostring(value),
		'.',
		{
			[0] = '०',
			[1] = '१',
			[2] = '२',
			[3] = '३',
			[4] = '४',
			[5] = '५',
			[6] = '६',
			[7] = '७',
			[8] = '८',
			[9] = '९',
		}
	)
	return text
end

return export