Module:ConvertNumeric
-- Module for converting between different representations of numbers. See talk page for user documentation.
 -- For unit tests see: [[Module:ConvertNumeric/testcases]]
 -- When editing, preview with: [[Module_talk:ConvertNumeric/testcases]]
 -- First, edit [[Module:ConvertNumeric/sandbox]], then preview with [[Module_talk:ConvertNumeric/sandbox/testcases]]
 
 local ones_position = {
 	[0] = 'சுழியம்',
 	[1] = 'ஒன்று',
 	[2] = 'இரண்டு',
 	[3] = 'மூன்று',
 	[4] = 'நான்கு',
 	[5] = 'ஐந்து',
 	[6] = 'ஆறு',
 	[7] = 'ஏழு',
 	[8] = 'எட்டு',
 	[9] = 'ஒன்பது',
 	[10] = 'பத்து',
 	[11] = 'eleven',
 	[12] = 'twelve',
 	[13] = 'thirteen',
 	[14] = 'fourteen',
 	[15] = 'fifteen',
 	[16] = 'sixteen',
 	[17] = 'seventeen',
 	[18] = 'eighteen',
 	[19] = 'nineteen'
 }
 
 
 local ones_position_ord = {
 	[0] = 'zeroth',
 	[1] = 'முதலாம்',
 	[2] = 'இரண்டாம்',
 	[3] = 'மூன்றாம்',
 	[4] = 'நான்காம்',
 	[5] = 'ஐந்தாம்',
 	[6] = 'ஆறாம்',
 	[7] = 'ஏழாம்',
 	[8] = 'எட்டாம்',
 	[9] = 'ஒன்பதாம்',
 	[10] = 'பத்தாம்',
 	[11] = 'பதினோராம்',
 	[12] = 'பன்னிரண்டாம்',
 	[13] = 'பதின்மூன்றாம்',
 	[14] = 'பதினான்காம்',
 	[15] = 'பதினைந்தாம்',
 	[16] = 'பதினாறாம்',
 	[17] = 'பதினேழாம்',
 	[18] = 'பதினெட்டாம்',
 	[19] = 'பத்தொன்பதாம்'
 }
 
 local ones_position_plural = {
 	[0] = 'zeros',
 	[1] = 'ones',
 	[2] = 'twos',
 	[3] = 'threes',
 	[4] = 'fours',
 	[5] = 'fives',
 	[6] = 'sixes',
 	[7] = 'sevens',
 	[8] = 'eights',
 	[9] = 'nines',
 	[10] = 'tens',
 	[11] = 'elevens',
 	[12] = 'twelves',
 	[13] = 'thirteens',
 	[14] = 'fourteens',
 	[15] = 'fifteens',
 	[16] = 'sixteens',
 	[17] = 'seventeens',
 	[18] = 'eighteens',
 	[19] = 'nineteens'
 }
 
 local tens_position = {
 	[2] = 'twenty',
 	[3] = 'thirty',
 	[4] = 'forty',
 	[5] = 'fifty',
 	[6] = 'sixty',
 	[7] = 'seventy',
 	[8] = 'eighty',
 	[9] = 'ninety'
 }
 
 local tens_position_ord = {
 	[2] = 'twentieth',
 	[3] = 'thirtieth',
 	[4] = 'fortieth',
 	[5] = 'fiftieth',
 	[6] = 'sixtieth',
 	[7] = 'seventieth',
 	[8] = 'eightieth',
 	[9] = 'ninetieth'
 }
 
 local tens_position_plural = {
 	[2] = 'twenties',
 	[3] = 'thirties',
 	[4] = 'forties',
 	[5] = 'fifties',
 	[6] = 'sixties',
 	[7] = 'seventies',
 	[8] = 'eighties',
 	[9] = 'nineties'
 }
 
 local groups = {
 	[1] = 'thousand',
 	[2] = 'million',
 	[3] = 'billion',
 	[4] = 'trillion',
 	[5] = 'quadrillion',
 	[6] = 'quintillion',
 	[7] = 'sextillion',
 	[8] = 'septillion',
 	[9] = 'octillion',
 	[10] = 'nonillion',
 	[11] = 'decillion',
 	[12] = 'undecillion',
 	[13] = 'duodecillion',
 	[14] = 'tredecillion',
 	[15] = 'quattuordecillion',
 	[16] = 'quindecillion',
 	[17] = 'sexdecillion',
 	[18] = 'septendecillion',
 	[19] = 'octodecillion',
 	[20] = 'novemdecillion',
 	[21] = 'vigintillion',
 	[22] = 'unvigintillion',
 	[23] = 'duovigintillion',
 	[24] = 'tresvigintillion',
 	[25] = 'quattuorvigintillion',
 	[26] = 'quinquavigintillion',
 	[27] = 'sesvigintillion',
 	[28] = 'septemvigintillion',
 	[29] = 'octovigintillion',
 	[30] = 'novemvigintillion',
 	[31] = 'trigintillion',
 	[32] = 'untrigintillion',
 	[33] = 'duotrigintillion',
 	[34] = 'trestrigintillion',
 	[35] = 'quattuortrigintillion',
 	[36] = 'quinquatrigintillion',
 	[37] = 'sestrigintillion',
 	[38] = 'septentrigintillion',
 	[39] = 'octotrigintillion',
 	[40] = 'noventrigintillion',
 	[41] = 'quadragintillion',
 	[51] = 'quinquagintillion',
 	[61] = 'sexagintillion',
 	[71] = 'septuagintillion',
 	[81] = 'octogintillion',
 	[91] = 'nonagintillion',
 	[101] = 'centillion',
 	[102] = 'uncentillion',
 	[103] = 'duocentillion',
 	[104] = 'trescentillion',
 	[111] = 'decicentillion',
 	[112] = 'undecicentillion',
 	[121] = 'viginticentillion',
 	[122] = 'unviginticentillion',
 	[131] = 'trigintacentillion',
 	[141] = 'quadragintacentillion',
 	[151] = 'quinquagintacentillion',
 	[161] = 'sexagintacentillion',
 	[171] = 'septuagintacentillion',
 	[181] = 'octogintacentillion',
 	[191] = 'nonagintacentillion',
 	[201] = 'ducentillion',
 	[301] = 'trecentillion',
 	[401] = 'quadringentillion',
 	[501] = 'quingentillion',
 	[601] = 'sescentillion',
 	[701] = 'septingentillion',
 	[801] = 'octingentillion',
 	[901] = 'nongentillion',
 	[1001] = 'millinillion',
 }
 
 local roman_numerals = {
 	I = 1,
 	V = 5,
 	X = 10,
 	L = 50,
 	C = 100,
 	D = 500,
 	M = 1000
 }
 
 local engord_tens_end  = {
 	['twentieth']	= 20,
 	['thirtieth']	= 30,
 	['fortieth']	= 40,
 	['fiftieth']	= 50,
 	['sixtieth']	= 60,
 	['seventieth']	= 70,
 	['eightieth']	= 80,
 	['ninetieth']	= 90,
 }
 
 local eng_tens_cont = {
 	['twenty']	= 20,
 	['thirty']	= 30,
 	['forty']	= 40,
 	['fifty']	= 50,
 	['sixty']	= 60,
 	['seventy']	= 70,
 	['eighty']	= 80,
 	['ninety']	= 90,
 }
 
 -- Converts a given valid roman numeral (and some invalid roman numerals) to a number. Returns { -1, errorstring } on error.
 local function roman_to_numeral(roman)
 	if type(roman) ~= &quot;string&quot; then return -1, &quot;roman numeral not a string&quot; end
 	local rev = roman:reverse()
 	local raising = true
 	local last = 0
 	local result = 0
 	for i = 1, #rev do
 		local c = rev:sub(i, i)
 		local next = roman_numerals[c]
 		if next == nil then return -1, &quot;roman numeral contains illegal character &quot; .. c end
 		if next &gt; last then
 			result = result + next
 			raising = true
 		elseif next &lt; last then
 			result = result - next
 			raising = false
 		elseif raising then
 			result = result + next
 		else
 			result = result - next
 		end
 		last = next
 	end
 	return result
 end
 
 -- Converts a given integer between 0 and 100 to English text (e.g. 47 -&gt; forty-seven).
 local function numeral_to_english_less_100(num, ordinal, plural, zero)
 	local terminal_ones, terminal_tens
 	if ordinal then
 		terminal_ones = ones_position_ord
 		terminal_tens = tens_position_ord
 	elseif plural then
 		terminal_ones = ones_position_plural
 		terminal_tens = tens_position_plural
 	else
 		terminal_ones = ones_position
 		terminal_tens = tens_position
 	end
 
 	if num == 0 and zero ~= nil then
 		return zero
 	elseif num &lt; 20 then
 		return terminal_ones[num]
 	elseif num % 10 == 0 then
 		return terminal_tens[num / 10]
 	else
 		return tens_position[math.floor(num / 10)] .. '-' .. terminal_ones[num % 10]
 	end
 end
 
 local function standard_suffix(ordinal, plural)
 	if ordinal then return 'th' end
 	if plural then return 's' end
 	return ''
 end
 
 -- Converts a given integer (in string form) between 0 and 1000 to English text (e.g. 47 -&gt; forty-seven).
 local function numeral_to_english_less_1000(num, use_and, ordinal, plural, zero)
 	num = tonumber(num)
 	if num &lt; 100 then
 		return numeral_to_english_less_100(num, ordinal, plural, zero)
 	elseif num % 100 == 0 then
 		return ones_position[num/100] .. ' hundred' .. standard_suffix(ordinal, plural)
 	else
 		return ones_position[math.floor(num/100)] .. ' hundred ' .. (use_and and 'and ' or '') .. numeral_to_english_less_100(num % 100, ordinal, plural, zero)
 	end
 end
 
 -- Converts an ordinal in English text from 'zeroth' to 'ninety-ninth' inclusive to a number [0–99], else -1.
 local function english_to_ordinal(english)
 	local eng = string.lower(english or '')
 
 	local engord_lt20 = {} -- ones_position_ord{} keys &amp; values swapped
 	for k, v in pairs( ones_position_ord ) do
 		engord_lt20[v] = k
 	end
 
 	if engord_lt20[eng] then
 		return engord_lt20[eng] -- e.g. first -&gt; 1
 	elseif engord_tens_end[eng] then
 		return engord_tens_end[eng] -- e.g. ninetieth -&gt; 90
 	else
 		local tens, ones = string.match(eng, '^([a-z]+)[%s%-]+([a-z]+)$')
 		if tens and ones then
 			local tens_cont = eng_tens_cont[tens]
 			local ones_end  = engord_lt20[ones]
 			if tens_cont and ones_end then
 				return tens_cont + ones_end -- e.g. ninety-ninth -&gt; 99
 			end
 		end
 	end
 	return -1 -- Failed
 end
 
 -- Converts a number in English text from 'zero' to 'ninety-nine' inclusive to a number [0–99], else -1.
 local function english_to_numeral(english)
 	local eng = string.lower(english or '')
 
 	local eng_lt20 = { ['single'] = 1 } -- ones_position{} keys &amp; values swapped
 	for k, v in pairs( ones_position ) do
 		eng_lt20[v] = k
 	end
 
 	if eng_lt20[eng] then
 		return eng_lt20[eng] -- e.g. one -&gt; 1
 	elseif eng_tens_cont[eng] then
 		return eng_tens_cont[eng] -- e.g. ninety -&gt; 90
 	else
 		local tens, ones = string.match(eng, '^([a-z]+)[%s%-]+([a-z]+)$')
 		if tens and ones then
 			local tens_cont = eng_tens_cont[tens]
 			local ones_end  = eng_lt20[ones]
 			if tens_cont and ones_end then
 				return tens_cont + ones_end -- e.g. ninety-nine -&gt; 99
 			end
 		end
 	end
 	return -1 -- Failed
 end
 
 -- Converts a number expressed as a string in scientific notation to a string in standard decimal notation
 -- e.g. 1.23E5 -&gt; 123000, 1.23E-5 = .0000123. Conversion is exact, no rounding is performed.
 local function scientific_notation_to_decimal(num)
 	local exponent, subs = num:gsub(&quot;^%-?%d*%.?%d*%-?[Ee]([+%-]?%d+)$&quot;, &quot;%1&quot;)
 	if subs == 0 then return num end  -- Input not in scientific notation, just return unmodified
 	exponent = tonumber(exponent)
 
 	local negative = num:find(&quot;^%-&quot;)
 	local _, decimal_pos = num:find(&quot;%.&quot;)
 	-- Mantissa will consist of all decimal digits with no decimal point
 	local mantissa = num:gsub(&quot;^%-?(%d*)%.?(%d*)%-?[Ee][+%-]?%d+$&quot;, &quot;%1%2&quot;)
 	if negative and decimal_pos then decimal_pos = decimal_pos - 1 end
 	if not decimal_pos then decimal_pos = #mantissa + 1 end
 
 	-- Remove leading zeros unless decimal point is in first position
 	while decimal_pos &gt; 1 and mantissa:sub(1,1) == '0' do
 		mantissa = mantissa:sub(2)
 		decimal_pos = decimal_pos - 1
 	end
 	-- Shift decimal point right for exponent &gt; 0
 	while exponent &gt; 0 do
 		decimal_pos = decimal_pos + 1
 		exponent = exponent - 1
 		if decimal_pos &gt; #mantissa + 1 then mantissa = mantissa .. '0' end
 		-- Remove leading zeros unless decimal point is in first position
 		while decimal_pos &gt; 1 and mantissa:sub(1,1) == '0' do
 			mantissa = mantissa:sub(2)
 			decimal_pos = decimal_pos - 1
 		end
 	end
 	-- Shift decimal point left for exponent &lt; 0
 	while exponent &lt; 0 do
 		if decimal_pos == 1 then
 			mantissa = '0' .. mantissa
 		else
 			decimal_pos = decimal_pos - 1
 		end
 		exponent = exponent + 1
 	end
 
 	-- Insert decimal point in correct position and return
 	return (negative and '-' or '') .. mantissa:sub(1, decimal_pos - 1) .. '.' .. mantissa:sub(decimal_pos)
 end
 
 -- Rounds a number to the nearest integer (NOT USED)
 local function round_num(x)
 	if x%1 &gt;= 0.5 then
 		return math.ceil(x)
 	else
 		return math.floor(x)
 	end
 end
 
 -- Rounds a number to the nearest two-word number (round = up, down, or &quot;on&quot; for round to nearest).
 -- Numbers with two digits before the decimal will be rounded to an integer as specified by round.
 -- Larger numbers will be rounded to a number with only one nonzero digit in front and all other digits zero.
 -- Negative sign is preserved and does not count towards word limit.
 local function round_for_english(num, round)
 	-- If an integer with at most two digits, just return
 	if num:find(&quot;^%-?%d?%d%.?$&quot;) then return num end
 
 	local negative = num:find(&quot;^%-&quot;)
 	if negative then
 		-- We're rounding magnitude so flip it
 		if round == 'up' then round = 'down' elseif round == 'down' then round = 'up' end
 	end
 
 	-- If at most two digits before decimal, round to integer and return
 	local _, _, small_int, trailing_digits, round_digit = num:find(&quot;^%-?(%d?%d?)%.((%d)%d*)$&quot;)
 	if small_int then
 		if small_int == '' then small_int = '0' end
 		if (round == 'up' and trailing_digits:find('[1-9]')) or (round == 'on' and tonumber(round_digit) &gt;= 5) then
 			small_int = tostring(tonumber(small_int) + 1)
 		end
 		return (negative and '-' or '') .. small_int
 	end
 
 	-- When rounding up, any number with &gt; 1 nonzero digit will round up (e.g. 1000000.001 rounds up to 2000000)
 	local nonzero_digits = 0
 	for digit in num:gfind(&quot;[1-9]&quot;) do
 		nonzero_digits = nonzero_digits + 1
 	end
 
 	num = num:gsub(&quot;%.%d*$&quot;, &quot;&quot;) -- Remove decimal part
 	-- Second digit used to determine which way to round lead digit
 	local _, _, lead_digit, round_digit, round_digit_2, rest = num:find(&quot;^%-?(%d)(%d)(%d)(%d*)$&quot;)
 	if tonumber(lead_digit .. round_digit) &lt; 20 and (1 + #rest) % 3 == 0 then
 		-- In English numbers &lt; 20 are one word so put 2 digits in lead and round based on 3rd
 		lead_digit = lead_digit .. round_digit
 		round_digit = round_digit_2
 	else
 		rest = round_digit_2 .. rest
 	end
 
 	if (round == 'up' and nonzero_digits &gt; 1) or (round == 'on' and tonumber(round_digit) &gt;= 5) then
 		lead_digit = tostring(tonumber(lead_digit) + 1)
 	end
 	-- All digits but lead digit will turn to zero
 	rest = rest:gsub(&quot;%d&quot;, &quot;0&quot;)
 	return (negative and '-' or '') .. lead_digit .. '0' .. rest
 end
 
 local denominators = {
 	[2] = { 'half', plural = 'halves' },
 	[3] = { 'third' },
 	[4] = { 'quarter', us = 'fourth' },
 	[5] = { 'fifth' },
 	[6] = { 'sixth' },
 	[8] = { 'eighth' },
 	[9] = { 'ninth' },
 	[10] = { 'tenth' },
 	[16] = { 'sixteenth' },
 }
 
 -- Return status, fraction where:
 -- status is a string:
 --     &quot;finished&quot; if there is a fraction with no whole number;
 --     &quot;ok&quot; if fraction is empty or valid;
 --     &quot;unsupported&quot; if bad fraction;
 -- fraction is a string giving (numerator / denominator) as English text, or is &quot;&quot;.
 -- Only unsigned fractions with a very limited range of values are supported,
 -- except that if whole is empty, the numerator can use &quot;-&quot; to indicate negative.
 -- whole (string or nil): nil or &quot;&quot; if no number before the fraction
 -- numerator (string or nil): numerator, if any (default = 1 if a denominator is given)
 -- denominator (string or nil): denominator, if any
 -- sp_us (boolean): true if sp=us
 -- negative_word (string): word to use for negative sign, if whole is empty
 -- use_one (boolean): false: 2+1/2 → &quot;two and a half&quot;; true: &quot;two and one-half&quot;
 local function fraction_to_english(whole, numerator, denominator, sp_us, negative_word, use_one)
 	if numerator or denominator then
 		local finished = (whole == nil or whole == '')
 		local sign = ''
 		if numerator then
 			if finished and numerator:sub(1, 1) == '-' then
 				numerator = numerator:sub(2)
 				sign = negative_word .. ' '
 			end
 		else
 			numerator = '1'
 		end
 		if not numerator:match('^%d+$') or not denominator or not denominator:match('^%d+$') then
 			return 'unsupported', ''
 		end
 		numerator = tonumber(numerator)
 		denominator = tonumber(denominator)
 		local dendata = denominators[denominator]
 		if not (dendata and 1 &lt;= numerator and numerator &lt;= 99) then
 			return 'unsupported', ''
 		end
 		local numstr, denstr
 		local sep = '-'
 		if numerator == 1 then
 			denstr = sp_us and dendata.us or dendata[1]
 			if finished or use_one then
 				numstr = 'one'
 			elseif denstr:match('^[aeiou]') then
 				numstr = 'an'
 				sep = ' '
 			else
 				numstr = 'a'
 				sep = ' '
 			end
 		else
 			numstr = numeral_to_english_less_100(numerator)
 			denstr = dendata.plural
 			if not denstr then
 				denstr = (sp_us and dendata.us or dendata[1]) .. 's'
 			end
 		end
 		if finished then
 			return 'finished', sign .. numstr .. sep .. denstr
 		end
 		return 'ok', ' and ' .. numstr .. sep .. denstr
 	end
 	return 'ok', ''
 end
 
 -- Takes a decimal number and converts it to English text.
 -- Return nil if a fraction cannot be converted (only some numbers are supported for fractions).
 -- num (string or nil): the number to convert.
 --      Can be an arbitrarily large decimal, such as &quot;-123456789123456789.345&quot;, and
 --      can use scientific notation (e.g. &quot;1.23E5&quot;).
 --      May fail for very large numbers not listed in &quot;groups&quot; such as &quot;1E4000&quot;.
 --      num is nil if there is no whole number before a fraction.
 -- numerator (string or nil): numerator of fraction (nil if no fraction)
 -- denominator (string or nil): denominator of fraction (nil if no fraction)
 -- capitalize (boolean): whether to capitalize the result (e.g. 'One' instead of 'one')
 -- use_and (boolean): whether to use the word 'and' between tens/ones place and higher places
 -- hyphenate (boolean): whether to hyphenate all words in the result, useful as an adjective
 -- ordinal (boolean): whether to produce an ordinal (e.g. 'first' instead of 'one')
 -- plural (boolean): whether to pluralize the resulting number
 -- links: nil: do not add any links; 'on': link &quot;billion&quot; and larger to Orders of magnitude article;
 --        any other text: list of numbers to link (e.g. &quot;billion,quadrillion&quot;)
 -- negative_word: word to use for negative sign (typically 'negative' or 'minus'; nil to use default)
 -- round: nil or '': no rounding; 'on': round to nearest two-word number; 'up'/'down': round up/down to two-word number
 -- zero: word to use for value '0' (nil to use default)
 -- use_one (boolean): false: 2+1/2 → &quot;two and a half&quot;; true: &quot;two and one-half&quot;
 local function _numeral_to_english(num, numerator, denominator, capitalize, use_and, hyphenate, ordinal, plural, links, negative_word, round, zero, use_one)
 	if not negative_word then
 		if use_and then
 			-- TODO Should 'minus' be used when do not have sp=us?
 			--      If so, need to update testcases, and need to fix &quot;minus zero&quot;.
 			-- negative_word = 'minus'
 			negative_word = 'negative'
 		else
 			negative_word = 'negative'
 		end
 	end
 	local status, fraction_text = fraction_to_english(num, numerator, denominator, not use_and, negative_word, use_one)
 	if status == 'unsupported' then
 		return nil
 	end
 	if status == 'finished' then
 		-- Input is a fraction with no whole number.
 		-- Hack to avoid executing stuff that depends on num being a number.
 		local s = fraction_text
 		if hyphenate then s = s:gsub(&quot;%s&quot;, &quot;-&quot;) end
 		if capitalize then s = s:gsub(&quot;^%l&quot;, string.upper) end
 		return s
 	end
 	num = scientific_notation_to_decimal(num)
 	if round and round ~= '' then
 		if round ~= 'on' and round ~= 'up' and round ~= 'down' then
 			error(&quot;Invalid rounding mode&quot;)
 		end
 		num = round_for_english(num, round)
 	end
 
 	-- Separate into negative sign, num (digits before decimal), decimal_places (digits after decimal)
 	local MINUS = '−'  -- Unicode U+2212 MINUS SIGN (may be in values from [[Module:Convert]])
 	if num:sub(1, #MINUS) == MINUS then
 		num = '-' .. num:sub(#MINUS + 1)  -- replace MINUS with '-'
 	elseif num:sub(1, 1) == '+' then
 		num = num:sub(2)  -- ignore any '+'
 	end
 	local negative = num:find(&quot;^%-&quot;)
 	local decimal_places, subs = num:gsub(&quot;^%-?%d*%.(%d+)$&quot;, &quot;%1&quot;)
 	if subs == 0 then decimal_places = nil end
 	num, subs = num:gsub(&quot;^%-?(%d*)%.?%d*$&quot;, &quot;%1&quot;)
 	if num == '' and decimal_places then num = '0' end
 	if subs == 0 or num == '' then error(&quot;Invalid decimal numeral&quot;) end
 
 	-- For each group of 3 digits except the last one, print with appropriate group name (e.g. million)
 	local s = ''
 	while #num &gt; 3 do
 		if s ~= '' then s = s .. ' ' end
 		local group_num = math.floor((#num - 1) / 3)
 		local group = groups[group_num]
 		local group_digits = #num - group_num*3
 		s = s .. numeral_to_english_less_1000(num:sub(1, group_digits), false, false, false, zero) .. ' '
 		if links and (((links == 'on' and group_num &gt;= 3) or links:find(group)) and group_num &lt;= 13) then
 			s = s .. '[[Orders_of_magnitude_(numbers)#10' .. group_num*3 .. '|' .. group .. ']]'
 		else
 			s = s .. group
 		end
 		num = num:sub(1 + group_digits)
 		num = num:gsub(&quot;^0*&quot;, &quot;&quot;)  -- Trim leading zeros
 	end
 
 	-- Handle final three digits of integer part
 	if s ~= '' and num ~= '' then
 		if #num &lt;= 2 and use_and then
 			s = s .. ' and '
 		else
 			s = s .. ' '
 		end
 	end
 	if s == '' or num ~= '' then
 		s = s .. numeral_to_english_less_1000(num, use_and, ordinal, plural, zero)
 	elseif ordinal or plural then
 		-- Round numbers like &quot;one million&quot; take standard suffixes for ordinal/plural
 		s = s .. standard_suffix(ordinal, plural)
 	end
 
 	-- For decimal places (if any) output &quot;point&quot; followed by spelling out digit by digit
 	if decimal_places then
 		s = s .. ' point'
 		for i = 1, #decimal_places do
 			s = s .. ' ' .. ones_position[tonumber(decimal_places:sub(i,i))]
 		end
 	end
 
 	s = s:gsub(&quot;^%s*(.-)%s*$&quot;, &quot;%1&quot;)   -- Trim whitespace
 	if ordinal and plural then s = s .. 's' end  -- s suffix works for all ordinals
 	if negative and s ~= zero then s = negative_word .. ' ' .. s end
 	s = s:gsub(&quot;negative zero&quot;, &quot;zero&quot;)
 	s = s .. fraction_text
 	if hyphenate then s = s:gsub(&quot;%s&quot;, &quot;-&quot;) end
 	if capitalize then s = s:gsub(&quot;^%l&quot;, string.upper) end
 	return s
 end
 
 local function _numeral_to_english2(args)
 	local num = args.num
 
 	if (not tonumber(num)) then
 		num = num:gsub(&quot;^%s*(.-)%s*$&quot;, &quot;%1&quot;)   -- Trim whitespace
 		num = num:gsub(&quot;,&quot;, &quot;&quot;)   -- Remove commas
 		num = num:gsub(&quot;^&lt;span[^&lt;&gt;]*&gt;&lt;/span&gt;&quot;, &quot;&quot;) -- Generated by Template:age
 		if num ~= '' then  -- a fraction may have an empty whole number
 			if not num:find(&quot;^%-?%d*%.?%d*%-?[Ee]?[+%-]?%d*$&quot;) then
 				-- Input not in a valid format, try to pass it through #expr to see
 				-- if that produces a number (e.g. &quot;3 + 5&quot; will become &quot;8&quot;).
 				num = mw.getCurrentFrame():preprocess('{{#expr: ' .. num .. '}}')
 			end
 		end
 	end
 
 	-- Pass args from frame to helper function
 	return _numeral_to_english(
 		num,
 		args['numerator'],
 		args['denominator'],
 		args['capitalize'],
 		args['use_and'],
 		args['hyphenate'],
 		args['ordinal'],
 		args['plural'],
 		args['links'],
 		args['negative_word'],
 		args['round'],
 		args['zero'],
 		args['use_one']
 	) or ''
 end
 
 local p = {  -- Functions that can be called from another module
 	roman_to_numeral = roman_to_numeral,
 	spell_number = _numeral_to_english,
 	spell_number2 = _numeral_to_english2,
 	english_to_ordinal = english_to_ordinal,
 	english_to_numeral = english_to_numeral,
 }
 
 function p._roman_to_numeral(frame) -- Callable via {{#invoke:ConvertNumeric|_roman_to_numeral|VI}}
 	return roman_to_numeral(frame.args[1])
 end
 
 function p._english_to_ordinal(frame) -- callable via {{#invoke:ConvertNumeric|_english_to_ordinal|First}}
 	return english_to_ordinal(frame.args[1])
 end
 
 function p._english_to_numeral(frame) -- callable via {{#invoke:ConvertNumeric|_english_to_numeral|One}}
 	return english_to_numeral(frame.args[1])
 end
 
 function p.numeral_to_english(frame)
 	local args = frame.args
 	local num = args[1]
 	num = num:gsub(&quot;^%s*(.-)%s*$&quot;, &quot;%1&quot;)   -- Trim whitespace
 	num = num:gsub(&quot;,&quot;, &quot;&quot;)   -- Remove commas
 	num = num:gsub(&quot;^&lt;span[^&lt;&gt;]*&gt;&lt;/span&gt;&quot;, &quot;&quot;) -- Generated by Template:age
 	if num ~= '' then  -- a fraction may have an empty whole number
 		if not num:find(&quot;^%-?%d*%.?%d*%-?[Ee]?[+%-]?%d*$&quot;) then
 			-- Input not in a valid format, try to pass it through #expr to see
 			-- if that produces a number (e.g. &quot;3 + 5&quot; will become &quot;8&quot;).
 			num = frame:preprocess('{{#expr: ' .. num .. '}}')
 		end
 	end
 
 	-- Pass args from frame to helper function
 	return _numeral_to_english(
 		num,
 		args['numerator'],
 		args['denominator'],
 		args['case'] == 'U' or args['case'] == 'u',
 		args['sp'] ~= 'us',
 		args['adj'] == 'on',
 		args['ord'] == 'on',
 		args['pl'] == 'on',
 		args['lk'],
 		args['negative'],
 		args['round'],
 		args['zero'],
 		args['one'] == 'one'  -- experiment: using '|one=one' makes fraction 2+1/2 give &quot;two and one-half&quot; instead of &quot;two and a half&quot;
 	) or ''
 end
 
 ---- recursive function for p.decToHex
 local function decToHexDigit(dec)
 	local dig = {&quot;0&quot;,&quot;1&quot;,&quot;2&quot;,&quot;3&quot;,&quot;4&quot;,&quot;5&quot;,&quot;6&quot;,&quot;7&quot;,&quot;8&quot;,&quot;9&quot;,&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;D&quot;,&quot;E&quot;,&quot;F&quot;}
 	local div = math.floor(dec/16)
 	local mod = dec-(16*div)
 	if div &gt;= 1 then return decToHexDigit(div)..dig[mod+1] else return dig[mod+1] end
 end -- I think this is supposed to be done with a tail call but first I want something that works at all
 
 ---- finds all the decimal numbers in the input text and hexes each of them
 function p.decToHex(frame)
 	local args=frame.args
 	local parent=frame.getParent(frame)
 	local pargs={}
 	if parent then pargs=parent.args end
 	local text=args[1] or pargs[1] or &quot;&quot;
 	local minlength=args.minlength or pargs.minlength or 1
 	minlength=tonumber(minlength)
 	local prowl=mw.ustring.gmatch(text,&quot;(.-)(%d+)&quot;)
 	local output=&quot;&quot;
 	repeat
 		local chaff,dec=prowl()
 		if not(dec) then break end
 		local hex=decToHexDigit(dec)
 		while (mw.ustring.len(hex)&lt;minlength) do hex=&quot;0&quot;..hex end
 		output=output..chaff..hex
 	until false
 	local chaff=mw.ustring.match(text,&quot;(%D+)$&quot;) or &quot;&quot;
 	return output..chaff
 end