-- Module for converting between different representations of numbers. See talk page for user documentation. -- For unit tests see: মডিউল:ConvertNumeric/testcases -- When editing, preview with: মডিউল আলাপ:ConvertNumeric/testcases -- First, edit মডিউল:ConvertNumeric/sandbox, then preview with মডিউল আলাপ:ConvertNumeric/sandbox/testcases  local ones_position = {     [0] = 'শূন্য',     [1] = 'এক',     [2] = 'দুই',     [3] = 'তিন',     [4] = 'চার',     [5] = 'পাঁচ',     [6] = 'ছয়',     [7] = 'সাত',     [8] = 'আট',     [9] = 'নয়',     [10] = 'দশ',     [11] = 'এগারো',     [12] = 'বারো',     [13] = 'তেরো',     [14] = 'চৌদ্দ',     [15] = 'পনেরো',     [16] = 'ষোলো',     [17] = 'সতেরো',     [18] = 'আঠারো',     [19] = 'উনিশ',     [20] = 'বিশ',     [21] = 'একুশ',     [22] = 'বাইশ',     [23] = 'তেইশ',     [24] = 'চব্বিশ',     [25] = 'পঁচিশ',     [26] = 'ছাব্বিশ',     [27] = 'সাতাশ',     [28] = 'আটাশ',     [29] = 'ঊনত্রিশ',     [30] = 'ত্রিশ',     [31] = 'একত্রিশ',     [32] = 'বত্রিশ',     [33] = 'তেত্রিশ',     [34] = 'চৌত্রিশ',     [35] = 'পঁয়ত্রিশ',     [36] = 'ছত্রিশ',     [37] = 'সাঁইত্রিশ',     [38] = 'আটত্রিশ',     [39] = 'ঊনচল্লিশ',     [40] = 'চল্লিশ',     [41] = 'একচল্লিশ',     [42] = 'বিয়াল্লিশ',     [43] = 'তেতাল্লিশ',     [44] = 'চুয়াল্লিশ',     [45] = 'পঁয়তাল্লিশ',     [46] = 'ছেচল্লিশ',     [47] = 'সাতচল্লিশ',     [48] = 'আটচল্লিশ',     [49] = 'ঊনপঞ্চাশ',     [50] = 'পঞ্চাশ',     [51] = 'একান্ন',     [52] = 'বাহান্ন',     [53] = 'তিপ্পান্ন',     [54] = 'চুয়ান্ন',     [55] = 'পঞ্চান্ন',     [56] = 'ছাপ্পান্ন',     [57] = 'সাতান্ন',     [58] = 'আটান্ন',     [59] = 'ঊনষাট',     [60] = 'ষাট',     [61] = 'একষট্টি',     [62] = 'বাষট্টি',     [63] = 'তেষট্টি',     [64] = 'চৌষট্টি',     [65] = 'পঁয়ষট্টি',     [66] = 'ছেষট্টি',     [67] = 'সাতষট্টি',     [68] = 'আটষট্টি',     [69] = 'ঊনসত্তর',     [70] = 'সত্তর',     [71] = 'একাত্তর',     [72] = 'বাহাত্তর',     [73] = 'তিয়াত্তর',     [74] = 'চুয়াত্তর',     [75] = 'পঁচাত্তর',     [76] = 'ছিয়াত্তর',     [77] = 'সাতাত্তর',     [78] = 'আটাত্তর',     [79] = 'ঊনআশি',     [80] = 'আশি',     [81] = 'একাশি',     [82] = 'বিরাশি',     [83] = 'তিরাশি',     [84] = 'চুরাশি',     [85] = 'পঁচাশি',     [86] = 'ছিয়াশি',     [87] = 'সাতাশি',     [88] = 'অষ্টআশি',     [89] = 'ঊননব্বই',     [90] = 'নব্বই',     [91] = 'একানব্বই',     [92] = 'বিরানব্বই',     [93] = 'তিরানব্বই',     [94] = 'চুরানব্বই',     [95] = 'পঁচানব্বই',     [96] = 'ছিয়ানব্বই',     [97] = 'সাতানব্বই',     [98] = 'আটানব্বই',     [99] = 'নিরানব্বই', }  local ones_position_ord = {     [0] = 'শূন্যতম',     [1] = 'প্রথম',     [2] = 'দ্বিতীয়',     [3] = 'তৃতীয়',     [4] = 'চতুর্থ',     [5] = 'পঞ্চম',     [6] = 'ষষ্ঠ',     [7] = 'সপ্তম',     [8] = 'অষ্টম',     [9] = 'নবম',     [10] = 'দশম',     [11] = 'একাদশ',     [12] = 'দ্বাদশ',     [13] = 'ত্রয়োদশ',     [14] = 'চতুর্দশ',     [15] = 'পঞ্চদশ',     [16] = 'ষোড়শ',     [17] = 'সপ্তদশ',     [18] = 'অষ্টাদশ',     [19] = 'ঊনবিংশ',     [20] = 'বিংশ',     [21] = 'একবিংশ',     [22] = 'দ্বাবিংশ',     [23] = 'ত্রয়োবিংশ',     [24] = 'চতুর্বিংশ',     [25] = 'পঞ্চবিংশ',     [26] = 'ষট্‌বিংশ',     [27] = 'সপ্তবিংশ',     [28] = 'অষ্টাবিংশ',     [29] = 'ঊনত্রিংশ',     [30] = 'ত্রিংশ',     [31] = 'একত্রিংশ',     [32] = 'দ্বাত্রিংশ',     [33] = 'ত্রয়োত্রিংশ',     [34] = 'চতুর্ত্রিংশ',     [35] = 'পঞ্চত্রিংশ',     [36] = 'ষট্‌ত্রিংশ',     [37] = 'সপ্তত্রিংশ',     [38] = 'অষ্টাত্রিংশ',     [39] = 'ঊনচত্বারিংশ',     [40] = 'চত্বারিংশ',     [41] = 'একচত্বারিংশ',     [42] = 'দ্বিচত্বারিংশ',     [43] = 'ত্রয়শ্চত্বারিংশ',     [44] = 'চতুঃচত্বারিংশ',     [45] = 'পঞ্চচত্বারিংশ',     [46] = 'ষট্‌চত্বারিংশ',     [47] = 'সপ্তচত্বারিংশ',     [48] = 'অষ্টচত্বারিংশ',     [49] = 'ঊনপঞ্চাশৎ',     [50] = 'পঞ্চাশৎ',     [51] = 'একপঞ্চাশৎ',     [52] = 'দ্বিপঞ্চাশৎ',     [53] = 'ত্রিপঞ্চাশৎ',     [54] = 'চতুঃপঞ্চাশৎ',     [55] = 'পঞ্চপঞ্চাশৎ',     [56] = 'ষট্‌পঞ্চাশৎ',     [57] = 'সপ্তপঞ্চাশৎ',     [58] = 'অষ্টপঞ্চাশৎ',     [59] = 'ঊনষষ্টি',     [60] = 'ষষ্টি',     [61] = 'একষষ্টি',     [62] = 'দ্বিষষ্টি',     [63] = 'ত্রিষষ্টি',     [64] = 'চতুঃষষ্টি',     [65] = 'পঞ্চষষ্টি',     [66] = 'ষট্‌ষষ্টি',     [67] = 'সপ্তষষ্টি',     [68] = 'অষ্টষষ্টি',     [69] = 'ঊনসপ্ততি',     [70] = 'সপ্ততি',     [71] = 'একসপ্ততি',     [72] = 'দ্বিসপ্ততি',     [73] = 'ত্রিসপ্ততি',     [74] = 'চতুঃসপ্ততি',     [75] = 'পঞ্চসপ্ততি',     [76] = 'ষট্‌সপ্ততি',     [77] = 'সপ্তসপ্ততি',     [78] = 'অষ্টসপ্ততি',     [79] = 'ঊনাশীতি',     [80] = 'অশীতি',     [81] = 'একাশীতি',     [82] = 'দ্ব্যশীতি',     [83] = 'ত্র্যশীতি',     [84] = 'চতুরশীতি',     [85] = 'পঞ্চাশীতি',     [86] = 'ষড়শীতি',     [87] = 'সপ্তাশীতি',     [88] = 'অষ্টাশীতি',     [89] = 'ঊননবতি',     [90] = 'নবতি',     [91] = 'একনবতি',     [92] = 'দ্বিনবতি',     [93] = 'ত্রিনবতি',     [94] = 'চতুর্নবতি',     [95] = 'পঞ্চনবতি',     [96] = 'ষন্নবতি',     [97] = 'সপ্তনবতি',     [98] = 'অষ্টনবতি',     [99] = 'নবনবতি' }  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] = 'বিশ', 	[3] = 'ত্রিশ', 	[4] = 'চল্লিশ', 	[5] = 'পঞ্চাশ', 	[6] = 'ষাট', 	[7] = 'সত্তর', 	[8] = 'আশি', 	[9] = 'নব্বই' }  local tens_position_ord = { 	[2] = 'বিংশতম', 	[3] = 'ত্রিশতম', 	[4] = 'চল্লিশতম', 	[5] = 'পঞ্চাশতম', 	[6] = 'ষাটতম', 	[7] = 'সত্তরতম', 	[8] = 'আশিতম', 	[9] = 'নব্বইতম' }  local tens_position_plural = { 	[2] = 'কুড়ি', 	[3] = 'thirties', 	[4] = 'forties', 	[5] = 'fifties', 	[6] = 'sixties', 	[7] = 'seventies', 	[8] = 'eighties', 	[9] = 'nineties' }  local groups = { 	[1] = 'হাজার', 	[2] = 'মিলিয়ন', 	[3] = 'বিলিয়ন', 	[4] = 'ট্রিলিয়ন', 	[5] = 'কোয়াড্রিলিয়ন', 	[6] = 'কুইন্টিলিয়ন', 	[7] = 'সেক্সটিলিয়ন', 	[8] = 'সেপ্টিলিয়ন', 	[9] = 'অক্টিলিয়ন', 	[10] = 'নোনিলিয়ন', 	[11] = 'ডেসিলিয়িন', 	[12] = 'উনডেসিলিয়ন', 	[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  = { 	['বিংশতম']  = 20, 	['ত্রিশতম']  = 30, 	['চল্লিশতম']   = 40, 	['পঞ্চাশতম']   = 50, 	['ষাটতম']   = 60, 	['সত্তরতম'] = 70, 	['আশিতম']  = 80, 	['নব্বইতম']  = 90, }  local eng_tens_cont = { 	['বিশ']  = 20, 	['ত্রিশ']  = 30, 	['চল্লিশ']   = 40, 	['পঞ্চাশ']   = 50, 	['ষাট']   = 60, 	['সত্তর'] = 70, 	['আশি']  = 80, 	['নব্বই']  = 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) ~= "string" then return -1, "roman numeral not a string" 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, "roman numeral contains illegal character " .. c end 		if next > last then 			result = result + next 			raising = true 		elseif next < 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 -> forty-seven). local function numeral_to_english_less_100(num, ordinal, plural, zero) 	local terminal_ones 	if ordinal then 		terminal_ones = ones_position_ord 	elseif plural then 		terminal_ones = ones_position_plural 	else 		terminal_ones = ones_position 	end  	if num == 0 and zero ~= nil then 		return zero 	elseif num < 100 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 'তম' 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 -> forty-seven). local function numeral_to_english_less_1000(num, use_and, ordinal, plural, zero) 	num = tonumber(num) 	if num < 100 then 		return numeral_to_english_less_100(num, ordinal, plural, zero) 	elseif num % 100 == 0 then 		return ones_position[num/100] .. ' শত' .. standard_suffix(ordinal, plural) 	else 		return ones_position[math.floor(num/100)] .. ' শত ' .. (use_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 & 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 -> 1 	elseif engord_tens_end[eng] then 		return engord_tens_end[eng] -- e.g. ninetieth -> 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 -> 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 & 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 -> 1 	elseif eng_tens_cont[eng] then 		return eng_tens_cont[eng] -- e.g. ninety -> 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 -> 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 -> 123000, 1.23E-5 = .0000123. Conversion is exact, no rounding is performed. local function scientific_notation_to_decimal(num) 	local exponent, subs = num:gsub("^%-?%d*%.?%d*%-?[Ee]([+%-]?%d+)$", "%1") 	if subs == 0 then return num end  -- Input not in scientific notation, just return unmodified 	exponent = tonumber(exponent)  	local negative = num:find("^%-") 	local _, decimal_pos = num:find("%.") 	-- Mantissa will consist of all decimal digits with no decimal point 	local mantissa = num:gsub("^%-?(%d*)%.?(%d*)%-?[Ee][+%-]?%d+$", "%1%2") 	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 > 1 and mantissa:sub(1,1) == '0' do 		mantissa = mantissa:sub(2) 		decimal_pos = decimal_pos - 1 	end 	-- Shift decimal point right for exponent > 0 	while exponent > 0 do 		decimal_pos = decimal_pos + 1 		exponent = exponent - 1 		if decimal_pos > #mantissa + 1 then mantissa = mantissa .. '0' end 		-- Remove leading zeros unless decimal point is in first position 		while decimal_pos > 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 < 0 	while exponent < 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 >= 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 "on" 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("^%-?%d?%d%.?$") then return num end  	local negative = num:find("^%-") 	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("^%-?(%d?%d?)%.((%d)%d*)$") 	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) >= 5) then 			small_int = tostring(tonumber(small_int) + 1) 		end 		return (negative and '-' or ) .. small_int 	end  	-- When rounding up, any number with > 1 nonzero digit will round up (e.g. 1000000.001 rounds up to 2000000) 	local nonzero_digits = 0 	for digit in num:gfind("[1-9]") do 		nonzero_digits = nonzero_digits + 1 	end  	num = num:gsub("%.%d*$", "") -- 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("^%-?(%d)(%d)(%d)(%d*)$") 	if tonumber(lead_digit .. round_digit) < 100 and (1 + #rest) % 3 == 0 then 		-- In English numbers < 100 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 > 1) or (round == 'on' and tonumber(round_digit) >= 5) then 		lead_digit = tostring(tonumber(lead_digit) + 1) 	end 	-- All digits but lead digit will turn to zero 	rest = rest:gsub("%d", "0") 	return (negative and '-' or ) .. lead_digit .. '0' .. rest end  local denominators = { 	[2] = { 'অর্ধেক', plural = 'অর্ধেক' }, 	[3] = { 'তৃতীয়াংশ' }, 	[4] = { 'চতুর্থাংশ', us = 'চতুর্থাংশ' }, 	[5] = { 'পঞ্চমাংশ' }, 	[6] = { 'ষষ্ঠাংশ' }, 	[8] = { 'অষ্টমাংশ' }, 	[9] = { 'মাংশ' }, 	[10] = { 'দশমাংশ' }, 	[16] = { 'ষোড়শ' }, }  -- Return status, fraction where: -- status is a string: --     "finished" if there is a fraction with no whole number; --     "ok" if fraction is empty or valid; --     "unsupported" if bad fraction; -- fraction is a string giving (numerator / denominator) as English text, or is "". -- Only unsigned fractions with a very limited range of values are supported, -- except that if whole is empty, the numerator can use "-" to indicate negative. -- whole (string or nil): nil or "" 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 → "two and a half"; true: "two and one-half" 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 <= numerator and numerator <= 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]) ..  			end 		end 		if finished then 			return 'finished', sign .. numstr .. sep .. denstr 		end 		return 'ok', ' এবং ' .. 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 "-123456789123456789.345", and --      can use scientific notation (e.g. "1.23E5"). --      May fail for very large numbers not listed in "groups" such as "1E4000". --      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 "billion" and larger to Orders of magnitude article; --        any other text: list of numbers to link (e.g. "billion,quadrillion") -- 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 → "two and a half"; true: "two and one-half" 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 "minus zero". 			-- negative_word = 'minus' 			negative_word = 'ঋণাত্মক' 		else 			negative_word = 'ঋণাত্মক' 		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("%s", "-") end 		if capitalize then s = s:gsub("^%l", 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("Invalid rounding mode") 		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("^%-") 	local decimal_places, subs = num:gsub("^%-?%d*%.(%d+)$", "%1") 	if subs == 0 then decimal_places = nil end 	num, subs = num:gsub("^%-?(%d*)%.?%d*$", "%1") 	if num ==  and decimal_places then num = '0' end 	if subs == 0 or num ==  then error("Invalid decimal numeral") end  	-- For each group of 3 digits except the last one, print with appropriate group name (e.g. million) 	local s =  	while #num > 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 >= 3) or links:find(group)) and group_num <= 13) then 			s = s .. '' .. group .. '' 		else 			s = s .. group 		end 		num = num:sub(1 + group_digits) 		num = num:gsub("^0*", "")  -- Trim leading zeros 	end  	-- Handle final three digits of integer part 	if s ~=  and num ~=  then 		if #num <= 2 and use_and then 			s = s .. ' ' 		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 "one million" take standard suffixes for ordinal/plural 		s = s .. standard_suffix(ordinal, plural) 	end  	-- For decimal places (if any) output "point" followed by spelling out digit by digit 	if decimal_places then 		s = s .. ' দশমিক' 		for i = 1, #decimal_places do 			s = s .. ' ' .. ones_position[tonumber(decimal_places:sub(i,i))] 		end 	end  	s = s:gsub("^%s*(.-)%s*$", "%1")   -- 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("ঋণাত্মক শূন্য", "শূন্য") 	s = s .. fraction_text 	if hyphenate then s = s:gsub("%s", "-") end 	if capitalize then s = s:gsub("^%l", string.upper) end 	return s end  local function _numeral_to_english2(args) 	local num = args.num  	if (not tonumber(num)) then 		num = num:gsub("^%s*(.-)%s*$", "%1")   -- Trim whitespace 		num = num:gsub(",", "")   -- Remove commas 		num = num:gsub("^<span[^<>]*></span>", "") -- Generated by Template:age 		if num ~=  then  -- a fraction may have an empty whole number 			if not num:find("^%-?%d*%.?%d*%-?[Ee]?[+%-]?%d*$") then 				-- Input not in a valid format, try to pass it through #expr to see 				-- if that produces a number (e.g. "3 + 5" will become "8"). 				num = mw.getCurrentFrame():preprocess('') 			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  	return roman_to_numeral(frame.args[1]) end  function p._english_to_ordinal(frame) -- callable via  	return english_to_ordinal(frame.args[1]) end  function p._english_to_numeral(frame) -- callable via  	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("^%s*(.-)%s*$", "%1")   -- Trim whitespace 	num = num:gsub(",", "")   -- Remove commas 	num = num:gsub("^<span[^<>]*></span>", "") -- Generated by Template:age 	if num ~=  then  -- a fraction may have an empty whole number 		if not num:find("^%-?%d*%.?%d*%-?[Ee]?[+%-]?%d*$") then 			-- Input not in a valid format, try to pass it through #expr to see 			-- if that produces a number (e.g. "3 + 5" will become "8"). 			num = frame:preprocess('') 		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 "two and one-half" instead of "two and a half" 	) or '' end   recursive function for p.decToHex local function decToHexDigit(dec) 	local dig = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"} 	local div = math.floor(dec/16) 	local mod = dec-(16*div) 	if div >= 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 "" 	local minlength=args.minlength or pargs.minlength or 1 	minlength=tonumber(minlength) 	local prowl=mw.ustring.gmatch(text,"(.-)(%d+)") 	local output="" 	repeat 		local chaff,dec=prowl() 		if not(dec) then break end 		local hex=decToHexDigit(dec) 		while (mw.ustring.len(hex)<minlength) do hex="0"..hex end 		output=output..chaff..hex 	until false 	local chaff=mw.ustring.match(text,"(%D+)$") or "" 	return output..chaff end  return p