Bước tới nội dung

Mô đun:ConvertNumeric

Bách khoa toàn thư mở Wikipedia

-- Mô đun để chuyển đổi qua lại những cách trình bày số. Xem tài liệu tại trang thảo luận.-- Có sẵn trường hợp thử thách tại: [[Mô đun:ConvertNumeric/testcases]]-- Sử dụng trang này để xem trước khi sửa đổi: [[Thảo luận Mô đun:ConvertNumeric/testcases]]-- Trước tiên sửa đổi [[Mô đun:ConvertNumeric/sandbox]] rồi xem trước dùng [[Thảo luận Mô đun:ConvertNumeric/sandbox/testcases]]local ones_position = {	[0] = 'zero',	[1] = 'one',	[2] = 'two',	[3] = 'three',	[4] = 'four',	[5] = 'five',	[6] = 'six',	[7] = 'seven',	[8] = 'eight',	[9] = 'nine',	[10] = 'ten',	[11] = 'eleven',	[12] = 'twelve',	[13] = 'thirteen',	[14] = 'fourteen',	[15] = 'fifteen',	[16] = 'sixteen',	[17] = 'seventeen',	[18] = 'eighteen',	[19] = 'nineteen'}local ones_position_vi = {	[0] = 'không',	[1] = 'một',	[2] = 'hai',	[3] = 'ba',	[4] = 'bốn',	[5] = 'năm',	[6] = 'sáu',	[7] = 'bảy',	[8] = 'tám',	[9] = 'chín',	[10] = 'mười',	[11] = 'mười một',	[12] = 'mười hai',	[13] = 'mười ba',	[14] = 'mười bốn',	[15] = 'mười lăm',	[16] = 'mười sáu',	[17] = 'mười bảy',	[18] = 'mười tám',	[19] = 'mười chín'}local ones_position_ord = {	[0] = 'zeroth',	[1] = 'first',	[2] = 'second',	[3] = 'third',	[4] = 'fourth',	[5] = 'fifth',	[6] = 'sixth',	[7] = 'seventh',	[8] = 'eighth',	[9] = 'ninth',	[10] = 'tenth',	[11] = 'eleventh',	[12] = 'twelfth',	[13] = 'thirteenth',	[14] = 'fourteenth',	[15] = 'fifteenth',	[16] = 'sixteenth',	[17] = 'seventeenth',	[18] = 'eighteenth',	[19] = 'nineteenth'}local ones_position_ord_vi = {	[0] = 'không',	[1] = 'nhất',	[2] = 'hai',	-- nhì	[3] = 'ba',	[4] = 'tư',	[5] = 'năm',	[6] = 'sáu',	[7] = 'bảy',	[8] = 'tám',	[9] = 'chín',	[10] = 'mười',	[11] = 'mười một',	[12] = 'mười hai',	[13] = 'mười ba',	[14] = 'mười bốn',	[15] = 'mười lăm',	[16] = 'mười sáu',	[17] = 'mười bảy',	[18] = 'mười tám',	[19] = 'mười chín'}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_vi = {	[2] = 'hai mươi',	[3] = 'ba mươi',	[4] = 'bốn mươi',	[5] = 'năm mươi',	[6] = 'sáu mươi',	[7] = 'bảy mươi',	[8] = 'tám mươi',	[9] = 'chín mươi'}local tens_position_ord = {	[2] = 'twentieth',	[3] = 'thirtieth',	[4] = 'fortieth',	[5] = 'fiftieth',	[6] = 'sixtieth',	[7] = 'seventieth',	[8] = 'eightieth',	[9] = 'ninetieth'}local tens_position_ord_vi = {	[2] = 'hai mươi',	[3] = 'ba mươi',	[4] = 'bốn mươi',	[5] = 'năm mươi',	[6] = 'sáu mươi',	[7] = 'bảy mươi',	[8] = 'tám mươi',	[9] = 'chín mươi'}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 groups_vi = {	[1] = 'ngàn',	-- nghìn	[2] = 'triệu',	[3] = 'tỷ',	-- tỉ	[4] = 'ngàn tỷ',	[5] = 'triệu tỷ',	[6] = 'tỷ tỷ',	[7] = 'ngàn tỷ tỷ',	[8] = 'triệu tỷ tỷ',	[9] = 'tỷ tỷ tỷ',	[10] = 'ngàn tỷ tỷ tỷ',	[11] = 'triệu tỷ tỷ tỷ',	[12] = 'tỷ tỷ tỷ tỷ',	[13] = 'ngàn tỷ tỷ tỷ tỷ',	[14] = 'triệu tỷ tỷ tỷ tỷ',}local vie_tens_cont = {	['hai mươi']	= 20,	['ba mươi']	    = 30,	['bôn mươi']	= 40,	['năm mươi']	= 50,	['sáu mươi']	= 60,	['bảy mươi']	= 70,	['tám mươi']	= 80,	['chín mươi']	= 90,}local roman_numerals = {	I = 1,	V = 5,	X = 10,	L = 50,	C = 100,	D = 500,	M = 1000}-- Converts a given valid roman numeral (and some invalid roman numerals) to a number. Returns -1, errorstring on errorlocal function roman_to_numeral(roman)	if type(roman) ~= "string" then return -1, "số La Mã không phải là chuỗi" 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, "số La Mã có ký tự không hợp lệ " .. 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 resultend-- 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, 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 < 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]	endendlocal function numeral_to_vietnamese_less_100(num, ordinal, plural, zero)	-- TODO: Chục	local terminal_ones	if ordinal then		terminal_ones = ones_position_ord_vi	else		terminal_ones = ones_position_vi	end	if num == 0 and zero ~= nil then		return zero	elseif num <= 10 then		return terminal_ones[num]	elseif num % 10 == 0 then		return terminal_ones[num / 10] .. ' mươi'	else		local ten = math.floor(num / 10)		local terminal_one = terminal_ones[num % 10]		local terminal_ten = ones_position_vi[ten] .. ' mươi'		if ten == 1 then			terminal_ten = terminal_ones[10]		elseif num % 10 == 1 then			terminal_one = "mốt"		end		if num % 10 == 5 then terminal_one = "lăm" end		return terminal_ten .. ' ' .. terminal_one	endendlocal 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 -> 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] .. ' 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)	endendlocal function numeral_to_vietnamese_less_1000(num, use_and, ordinal, plural, zero, half)	num = tonumber(num)	if num < 100 then		return numeral_to_vietnamese_less_100(num, ordinal, plural, zero)	elseif num % 100 == 0 then		return ones_position_vi[num/100] .. ' trăm'	elseif half and num % 100 == 50 then		return ones_position_vi[math.floor(num/100)] .. ' trăm rưỡi'	elseif num % 100 <= 10 then		local terminal_ones		if ordinal then			terminal_ones = ones_position_ord_vi		else			terminal_ones = ones_position_vi		end		return ones_position_vi[math.floor(num/100)] .. ' trăm lẻ ' .. terminal_ones[num % 100]	-- linh	else		return ones_position_vi[math.floor(num/100)] .. ' trăm ' .. numeral_to_vietnamese_less_100(num % 100, ordinal, plural, zero)	endend-- 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	local prev_len = #num	-- 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)	endend-- 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		local small_int_len = #small_int		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) < 20 and (1 + #rest) % 3 == 0 then		-- In English numbers < 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 > 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' .. restendlocal denominators = {	[2] = { 'half', plural = 'halves' },	[3] = { 'third' },	[4] = { 'quarter', us = 'fourth' },	[5] = { 'fifth' },	[6] = { 'sixth' },	[8] = { 'eighth' },	[10] = { 'tenth' },	[16] = { 'sixteenth' },}-- 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]) .. 's'			end		end		if finished then			return 'finished', sign .. numstr .. sep .. denstr		end		return 'ok', ' and ' .. numstr .. sep .. denstr	end	return 'ok', ''endlocal function fraction_to_vietnamese(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)		if numerator > 0 and numerator * 2 == denominator then			return 'ok', ' rưỡi'		end		if not (1 <= numerator and numerator <= 999 and denominator <= 999) then			return 'unsupported', ''		end		local numstr = numeral_to_vietnamese_less_1000(numerator)		local denstr = numeral_to_vietnamese_less_1000(denominator, false, true)		denstr = denstr:gsub("^một ", "")		if finished then			return 'finished', sign .. numstr .. ' phần ' .. denstr		end		return 'ok', ' ' .. numstr .. ' phần ' .. 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 for use 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 = 'negative'		else			negative_word = 'negative'		end	end	local status, fraction_text = fraction_to_english(num, numerator, denominator, not use_and, negative_word)	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			return 'Chế độ làm tròn không hợp lệ'		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 [[Mô đun: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 return 'Chữ số thập phân không hợp lệ' 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 .. '[[Bậc độ lớn (số)#10' .. group_num*3 .. '|' .. 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 .. ' 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 "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 .. ' point'		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("negative zero", "zero")	s = s .. fraction_text	if hyphenate then s = s:gsub("%s", "-") end	if capitalize then s = s:gsub("^%l", string.upper) end	return sendlocal function _numeral_to_vietnamese(num, numerator, denominator, capitalize, use_and, hyphenate, ordinal, plural, links, negative_word, round, zero, use_one)	if not negative_word then		negative_word = 'âm'	end	local status, fraction_text = fraction_to_vietnamese(num, numerator, denominator, not use_and, negative_word)	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 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			return 'Chế độ làm tròn không hợp lệ'		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 [[Mô đun: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 return 'Chữ số thập phân không hợp lệ' end		local entire_num = num	-- 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_vi[group_num]		local group_digits = #num - group_num*3		if tonumber(num:gsub("^500", ""), 10)  == 0 and tonumber(num:gsub("^1000", ""), 10) == 0 then			s = s .. "rưỡi"		else			s = s .. numeral_to_vietnamese_less_1000(num:sub(1, group_digits), false, false, false, zero, false) .. ' '			if links and (((links == 'on' and group_num >= 3) or links:find(group)) and group_num <= 13) then				s = s .. '[[Bậc độ lớn (số)#10' .. group_num*3 .. '|' .. group .. ']]'			else				s = s .. group			end		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		s = s .. ' '	end	if tonumber(entire_num) > 500 and tonumber(num) == 500 then		s = s .. "rưỡi"	elseif s == '' or num ~= '' then		if tonumber(entire_num) > 1000 and tonumber(num) <= 10 then			s = s .. "lẻ "		end		s = s .. numeral_to_vietnamese_less_1000(num, use_and, ordinal, plural, zero, #entire_num <= 3)	end	-- For decimal places (if any) output "point" followed by spelling out digit by digit	if decimal_places then		s = s .. ' phẩy'		for i = 1, #decimal_places do			s = s .. ' ' .. ones_position_vi[tonumber(decimal_places:sub(i,i))]		end	end	s = s:gsub("^%s*(.-)%s*$", "%1")   -- Trim whitespace	if negative and s ~= zero then s = negative_word .. ' ' .. s end	s = s:gsub("âm không", "không")	s = s .. fraction_text	if capitalize then s = s:gsub("^%l", string.upper) end	return sendlocal function _numeral_to_vietnamese2(args)	local num = tostring(args.num)	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 eval it as an expr to see			-- if that produces a number (e.g. "3 + 5" will become "8").			local noerr, result = pcall(mw.ext.ParserFunctions.expr, num)			if noerr then				num = result			end		end	end	-- Call helper function passing args	return _numeral_to_vietnamese(		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 ''endlocal function vietnamese_to_numeral(vietnamese)	local vie = string.lower(vietnamese or '')	local vie_lt20 = {} -- hoán đổi khoá và giá trị của ones_position_vi{}	for k, v in pairs( ones_position_vi ) do		vie_lt20[v] = k	end	local vie_lt20_special = vie_lt20	vie_lt20_special['mốt'] = 1	if vie_lt20[vie] then		return vie_lt20[vie] -- e.g. một -> 1	elseif vie_tens_cont[vie] then		return vie_tens_cont[vie] -- e.g. chín mươi -> 90	else		local tens, ones = string.match(vie, '^(.+%s.+)%s+(.+)$')		if tens and ones then			local tens_cont = vie_tens_cont[tens]			local ones_end  = vie_lt20_special[ones]			if tens_cont and ones_end then				return tens_cont + ones_end -- e.g. chín mươi chín -> 99			end		end	end	return -1 -- Failedendlocal p = {	-- functions that can be called from another module	roman_to_numeral = roman_to_numeral,	spell_number = _numeral_to_vietnamese,	spell_number2 = _numeral_to_vietnamese2,	vietnamese_to_numeral = vietnamese_to_numeral,}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	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('{{#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 "two and one-half" instead of "two and a half"	) or ''endfunction p.numeral_to_vietnamese(frame)	local args = frame.args	local num = args[1]	num = mw.text.trim(num)   -- Trim whitespace	num = num:gsub("%.", ""):gsub(",", ".")   -- Convert to computer format	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('{{#expr: ' .. num .. '}}')		end	end	-- Pass args from frame to helper function	local numerator = mw.text.trim(args['numerator'] or args[2])	if #numerator == 0 then numerator = nil end	local denominator = mw.text.trim(args['denominator'] or args[3])	if #denominator == 0 then denominator = nil end	local case = args['case']	local sp = args['sp']	local adj = args['adj']	local ord = args['ord']	local pl = args['pl']	local lk = args['lk']	local negative = args['negative']	local round = args['round']	local zero = args['zero']	return _numeral_to_vietnamese(num, numerator, denominator,		case == 'U' or case == 'u', sp ~= 'us', adj == 'on',		ord == 'on', pl == 'on', lk, negative, round, zero) or ''end---- recursive function for p.decToHexlocal 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] endend -- 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 themfunction 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..chaffend--p.numeral_to_vietnamese_less_100 = numeral_to_vietnamese_less_100				-- debug--p.numeral_to_vietnamese_less_1000 = numeral_to_vietnamese_less_1000				-- debugreturn p