跳至內容

模組:Num2Chinese

今本(此為底本,未經審校)
文出維基大典

可在模組:Num2Chinese/doc建立此模組的說明文件

local err = require("Module:Error")

local mapping = {
	["0"] = "〇",
	["1"] = "一",
	["2"] = "二",
	["3"] = "三",
	["4"] = "四",
	["5"] = "五",
	["6"] = "六",
	["7"] = "七",
	["8"] = "八",
	["9"] = "九",
	["."] = "點",
	["+"] = "正",
	["-"] = "負",
	["%"] = "厘",
	[" "] = "",
	[","] = "",
}

local mapping_digit = {
	{[0] = "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "百", "千"},
	{[0] = "零", "壹", "貳", "叄", "肆", "伍", "陸", "柒", "捌", "玖", "拾", "佰", "仟"},
}

local units = {
	"萬", "億",  -- 大陆通行
	"兆", "京", "垓", "秭", "穰", "溝", "澗", "正", "載", "極",  -- 传统记数
	-- "恆河沙", "阿僧祇", "那由他", "不可思議", "無量", "大數",
}

local units_minor = {
	"分", "厘", "毫", "絲", "忽", "微", "纖", "沙", "塵", "埃", "渺", "漠",  -- 传统小数
	-- "模糊", "逡巡", "須臾", "瞬息", "彈指", "刹那", "六德", "虛空", "清净", "阿賴耶", "阿摩羅", "涅槃寂靜",
}

-- 将数字逐位转换,不添加单位,原样保留其他字符
local function _digits(s)
	local ans = {}
	for c in string.gmatch(s, ".") do
		local mapped = mapping[c]
		if mapped == nil then
			mapped = c
		end
		table.insert(ans, mapped)
	end
	return table.concat(ans)
end

-- 将数字逐位转换,不添加单位,去除所有逗号、空格,改为小数点前后每隔len位空格分隔
local function _digits_segmented(s, len)
	if type(len) ~= "number" or len <= 0 then
		return _digits(s)
	end
	local ans = {}
	if string.sub(s, 1, 1) == '-' then
		table.insert(ans, '負')
		s = string.sub(s, 2)
	end
	s = string.gsub(s, "[,%s]", "")  -- filter comma, space
	local pos = string.find(s, '.', 1, true)
	if pos == nil then pos = #s + 1 end

	local countdown = (pos - 2) % len + 2
	for c in string.gmatch(s, ".") do
		if c == '.' then
			countdown = len + 1
		else
			countdown = countdown - 1
			if countdown == 0 then
				table.insert(ans, " ")
				countdown = len
			end
		end
		table.insert(ans, mapping[c])
	end
	return table.concat(ans)
end

-- 一万以内的整数,使用map对应的中文数字,ling代替大小位分隔“零”
local function _number_small(s, map, ling)
	local n = tonumber(s, 10)
	if n == 0 then
		if ling == '〇' then
			return '〇'
		else
			return "零"
		end
	end
	assert(n > 0 and n < 10000, "超出范围:" .. s)
	local ans = {}
	if n >= 1000 then
		table.insert(ans, map[math.floor(n/1000)] .. map[12])
		n = n%1000
		if 0 < n and n < 100 then
			table.insert(ans, ling)
		end
	end
	if n >= 100 then
		table.insert(ans, map[math.floor(n/100)] .. map[11])
		n = n%100
		if 0 < n and n < 10 then
			table.insert(ans, ling)
		end
	end
	local tens = 0
	if n >= 10 then
		tens = math.floor(n/10)
		if #ans ~= 0 or tens ~= 1 or string.sub(s, 1, 1) == '0' then
			table.insert(ans, map[tens] .. map[10])
		else
			table.insert(ans, map[10])
		end
		n = n%10
	end
	if n > 0 then
		table.insert(ans, map[n])
	end
	return table.concat(ans)
end

-- 所有实数,长度不限,可以空格、逗号分隔,len=-1采用传统小数记法,否则小数部分按位分隔;
-- daxie=1采用大写,ling代替大小位分隔“零”
local function _number(s, len, daxie, ling)
	assert(type(len) == "number")
	if ling == nil then ling = "零" end
	local ans = {}
	local map = mapping_digit[1]
	if daxie == "1" then map = mapping_digit[2] end

	if string.sub(s, 1, 1) == '-' then
		table.insert(ans, '負')
		s = string.sub(s, 2)
	end
	s = string.gsub(s, "[,%s]", "")  -- filter comma, space
	if not string.match(s,'^%d+%.?%d*$') then  -- TODO 科学计数法
		return err.error{ message = "非數字:" .. s }
	end
	local pos = string.find(s, '.', 1, true)
	if pos == nil then pos = #s + 1 end

	-- integer part
	-- print("pos", pos)
	local i_unit = math.floor((pos - 2) / 4)
	for i = (pos - 2) % 4 - 2, pos - 4, 4 do  -- 将小数点前数字每四位分隔
		i_unit = i_unit - 1
		if i <= 0 then  -- first segment
			-- print(i, i_unit, string.sub(s, 1, i+3))
			table.insert(ans, _number_small(string.sub(s, 1, i+3), map, ling))
		else
			local small = string.sub(s, i, i+3)
			-- print(i, i_unit, small)
			if string.sub(small, 1, 1) == '0' and ans[#ans] ~= ling then
				table.insert(ans, ling)
			end
			if small ~= "0000" then
				table.insert(ans, _number_small(small, map, ling))
			end
		end
		-- add units
		if i_unit ~= -1 then
			if ans[#ans] == ling then  -- max unit loop
				if i_unit % #units + 1 == #units then
					table.insert(ans, #ans, units[#units])
				end
			else
				table.insert(ans, units[i_unit % #units + 1])
			end
		end
	end
	if ans[#ans] == ling and pos > 2 then
		table.remove(ans, #ans)
	end

	-- fraction
	if len < 0 then  -- 传统小数
		local frac = string.sub(s, pos+1)
		if ans[#ans] == "零" and #frac > 0 then  -- 不以“零又”开头
			table.remove(ans, #ans)
		else
			table.insert(ans, '又')
		end
		for i = 1, #units_minor do
			if i > #frac then
				break
			end
			local digit = string.sub(frac, i, i)
			if digit ~= '0' then
				table.insert(ans, map[string.byte(digit) - 48] .. units_minor[i])
			elseif i == #units_minor then  -- 最后一个可用单位
				table.insert(ans, "零" .. units_minor[i])
			end
		end
		if #units_minor < #frac then
			frac = _digits(string.sub(frac, #units_minor))
			if ling ~= '〇' then
				frac = frac:gsub('〇', '零')
			end
			table.insert(ans, frac)
		end
		if ans[#ans] == '又' then  -- 若无小数,移除
			table.remove(ans, #ans)
		end
	else
		local frac = _digits_segmented(string.sub(s, pos), len)
		if ling ~= '〇' then
			frac = frac:gsub('〇', '零')
		end
		table.insert(ans, frac)
	end

	return table.concat(ans)
end

local p = {}

function p.digits(frame)
	local s = frame.args[1]
	local len = tonumber(frame.args.len)
	if type(len) == "number" then
		return _digits_segmented(s, len)
	else
		return _digits(s)
	end
end

function p.number(frame)
	local s = frame.args[1]
	local len = tonumber(frame.args.len)
	if len == nil then len = 0 end
	return _number(s, len, frame.args.daxie, frame.args.ling)
end

return p