Módulo:FormatNum

Fonte: Enciclopédia de conhecimento da Igreja de Deus
Saltar para a navegação Saltar para a pesquisa

A documentação para este módulo pode ser criada na página Módulo:FormatNum/doc

--[[ 2013-06-16
FormatNum
* format
* round
FormatNum()
]]
local FormatNum = { };
-- Constant for round method "round half to even" (IEEE 754).
local ROUND_TO_EVEN = 0;
-- Constant for round method "round half away from zero"
-- also filters "-0" and converts it to "0".
local ROUND_AWAY_FROM_ZERO = 1;
-- Table storing the format options.
local FORMAT_TABLE = {};
-- Format table for "pt".
FORMAT_TABLE.pt = {};
FORMAT_TABLE.pt.decimalMark = ",";
FORMAT_TABLE.pt.groupMark = " ";
FORMAT_TABLE.pt.groupMinLength = 5;
FORMAT_TABLE.pt.groupOnlyIntegerPart = false;
-- Format table for "pt_currency".
FORMAT_TABLE.pt_currency = {};
FORMAT_TABLE.pt_currency.decimalMark = ",";
FORMAT_TABLE.pt_currency.groupMark = ".";
FORMAT_TABLE.pt_currency.groupMinLength = 5;
FORMAT_TABLE.pt_currency.groupOnlyIntegerPart = true;
-- Format table for "en".
FORMAT_TABLE.en = {};
FORMAT_TABLE.en.decimalMark = ".";
FORMAT_TABLE.en.groupMark = ",";
FORMAT_TABLE.en.groupMinLength = 4;
FORMAT_TABLE.en.groupOnlyIntegerPart = true;
-- Format table for "iso31_0" (ISO 31-0 using comma as decimal mark).
FORMAT_TABLE.iso31_0 = {};
FORMAT_TABLE.iso31_0.decimalMark = ",";
FORMAT_TABLE.iso31_0.groupMark = " ";
FORMAT_TABLE.iso31_0.groupMinLength = 4;
FORMAT_TABLE.iso31_0.groupOnlyIntegerPart = false;
-- Format table for "iso31_0_point" (ISO 31-0 using point as decimal mark).
FORMAT_TABLE.iso31_0_point = {};
FORMAT_TABLE.iso31_0_point.decimalMark = ".";
FORMAT_TABLE.iso31_0_point.groupMark = " ";
FORMAT_TABLE.iso31_0_point.groupMinLength = 4;
FORMAT_TABLE.iso31_0_point.groupOnlyIntegerPart = false;
-- Format table for "pc" (simply nil to prevent formatting).
FORMAT_TABLE.pc = nil;
-- Format table for "comma" (no grouping - groupMark "").
FORMAT_TABLE.comma = {};
FORMAT_TABLE.comma.decimalMark = ",";
FORMAT_TABLE.comma.groupMark = "";
FORMAT_TABLE.comma.groupMinLength = 1000; -- (for performance, but also small values wouldn't matter)
FORMAT_TABLE.comma.groupOnlyIntegerPart = true;
-- Constant defining digit group lenghts when digit grouping is used.
local DIGIT_GROUPING_SIZE = 3;
--[[
				Internal used function for rounding.
				@param a_number : Number to be rounded.
				@param a_precision : Number of significant digits of the fractional part. If it
							is negative, the according number of digits of the integer part are also
							rounded.
				@param a_roundMethod : Numeric constant defining the round method to use.
							Supported are ROUND_TO_EVEN and ROUND_AWAY_FROM_ZERO.
				@return String of the rounded number like returned by Lua function string.format().
]]
local function numberToString(a_number, a_precision, a_roundMethod)
				if (a_precision < 0) then
								a_precision = -a_precision;
								if (a_roundMethod == ROUND_TO_EVEN) then
												local integerPart = math.floor(math.abs(a_number) / (10 ^ a_precision));
												if (integerPart % 2 == 0) then
																-- next even number smaller than a_number / 10^precision
																a_number = a_number - 5 * (10 ^ (a_precision - 1));
																a_number = math.ceil(a_number / (10 ^ a_precision)) * (10 ^ a_precision);
												else
																-- next even number bigger than a_number / 10^precision
																a_number = a_number + 5 * (10 ^ (a_precision - 1));
																a_number = math.floor(a_number / (10 ^ a_precision)) * (10 ^ a_precision);
												end
								elseif (a_roundMethod == ROUND_AWAY_FROM_ZERO) then
												if (a_number >= 0) then
																a_number = a_number + 5 * (10 ^ (a_precision - 1));
																a_number = math.floor(a_number / (10 ^ a_precision)) * (10 ^ a_precision);
												else
																a_number = a_number - 5 * (10 ^ (a_precision - 1));
																a_number = math.ceil(a_number / (10 ^ a_precision)) * (10 ^ a_precision);
												end
								end
								-- handle it as normal integer
								a_precision = 0;
				end
				if (a_roundMethod == ROUND_AWAY_FROM_ZERO) then
								if ((a_number * (10 ^ a_precision)) - math.floor(a_number * (10 ^ a_precision)) == 0.5) then
												-- because string.format() uses round to even, we have to add (numbers >0) or
												-- subtract (numbers <0) a little bit to point into the "correct" rounding
												-- direction if a_number is exactly in the middle between two rounded numbers
												if (a_number >= 0) then
																a_number = a_number + (10 ^ -(a_precision + 1));
												else
																a_number = a_number - (10 ^ -(a_precision + 1));
												end
								else
												if (math.abs(a_number * (10 ^ a_precision)) < 0.5) then
																-- filter "-0" and convert it to 0
																a_number = math.abs(a_number);
												end
								end
				end
				return string.format("%." .. tostring(a_precision) .. "f", a_number);
end -- numberToString()
--[[
				Internal used function for formatting.
				@param a_number : String of a non-negative number to be formatted.
				@param a_decimalMark : String of the decimal mark to use.
				@param a_groupMark : String of the mark used for digit grouping.
				@param a_groupMinLength : Number defining the minimum length of integer part
							to use digit grouping (normally 4 or 5). However if fractional part is
							longer than DIGIT_GROUPING_SIZE (3 as default) and digit grouping of
							fractional part is not disabled via 'a_groupOnlyIntegerPart', then this
							value is ignored and set to DIGIT_GROUPING_SIZE + 1.
				@param a_groupOnlyIntegerPart : Boolean defining whether activating digit
							grouping only for integer part (true) or for integer and fractional part
							(false).
				@return String of the formatted number according to the parameters.
]]
local function formatNumber(a_number, a_decimalMark, a_groupMark, a_groupMinLength, a_groupOnlyIntegerPart)
				-- find the decimal point
				local decimalPosition = mw.ustring.find(a_number, ".", 1, true);
				local needsGrouping = false;
				if (not decimalPosition) then
								-- no decimal point - integer number
								decimalPosition = mw.ustring.len(a_number) + 1;
								if (decimalPosition > a_groupMinLength) then
												needsGrouping = true;
								end
				else
								-- decimal point present
								if ((decimalPosition > a_groupMinLength) or (((mw.ustring.len(a_number) - decimalPosition) > DIGIT_GROUPING_SIZE) and (not a_groupOnlyIntegerPart))) then
												needsGrouping = true;
								end
								-- replace the decimal point
								a_number = mw.ustring.sub(a_number, 1, decimalPosition - 1) .. a_decimalMark .. mw.ustring.sub(a_number, decimalPosition + 1);
				end
				if (needsGrouping and (decimalPosition > DIGIT_GROUPING_SIZE + 1)) then
								-- grouping of integer part necessary
								local i = decimalPosition - DIGIT_GROUPING_SIZE;
								while (i > 1) do
												-- group the integer part
												a_number = mw.ustring.sub(a_number, 1, i - 1) .. a_groupMark .. mw.ustring.sub(a_number, i);
												decimalPosition = decimalPosition + mw.ustring.len(a_groupMark);
												i = i - DIGIT_GROUPING_SIZE;
								end
				end
				-- skip to the end of the new decimal mark (in case it is more than one char)
				decimalPosition = decimalPosition + mw.ustring.len(a_decimalMark) - 1;
				if (a_groupOnlyIntegerPart) then
								needsGrouping = false;
				end
				if (needsGrouping and ((mw.ustring.len(a_number) - decimalPosition) > DIGIT_GROUPING_SIZE)) then
								-- grouping of fractional part necessary
								-- using negative numbers (index from the end of the string)
								local i = decimalPosition - mw.ustring.len(a_number) + DIGIT_GROUPING_SIZE;
								while (i <= -1) do
												-- group the fractional part
												a_number = mw.ustring.sub(a_number, 1, i - 1) .. a_groupMark .. mw.ustring.sub(a_number, i);
												i = i + DIGIT_GROUPING_SIZE;
								end
				end
				return a_number;
end -- formatNumber()
--[[
				Formatting numbers.
				@param source : String representation
											of an unformatted (but maybe rounded) floating point or integer number.
				@param spec : Formatting option. Currently there are
											"at", "comma", "de", "dewiki", "de_currency", "ch", "ch_currency", "en", "iso31_0",
											"iso31_0_point" and "pc" supported. See the FORMAT_TABLE for details.
				@return String of the formatted number.
												If the argument 'spec' is invalid
												or 'source' is not a valid string representation of a number,
												'source' is returned unmodified.
]]
function FormatNum.format(source, spec)
				local number;
				if type(source) == "string" then
								number = mw.text.trim(source);
				end
				if not spec or spec == "" then
								spec = "dewiki"
				end
				if (number and spec) then
								local format = FORMAT_TABLE[spec];
								if (format) then
												-- format entry found
												local sign = mw.ustring.sub(number, 1, 1);
												if ((sign == "+") or (sign == "-")) then
																-- remove sign from number, add it later again
																number = mw.ustring.sub(number, 2);
												else
																-- was not a sign
																sign = "";
												end
												if (mw.ustring.sub(number, 1, 1) == ".") then
																-- number begins with "." -> add a 0 to the beginning
																number = "0" .. number;
												else
																if (mw.ustring.sub(number, -1) == ".") then
																				-- number ends with "." -> remove it
																				number = mw.ustring.sub(number, 1, -2);
																end
												end
												if ((number == mw.ustring.match(number, "^%d+$")) or (number == mw.ustring.match(number, "^%d+%.%d+$"))) then
																-- number has valid format (only digits or digits.digits) -> format it and add sign (if any) again
																number = sign .. formatNumber(number, format.decimalMark, format.groupMark, format.groupMinLength, format.groupOnlyIntegerPart);
												else
																-- number has no valid format -> undo all modifications
																number = source;
												end
								end
				end
				return number;
end -- FormatNum.format()
--[[
				Rounding numbers.
				@param number : string with unformatted floating point or integer number.
				@param precision : number of significant fractional part digits.
											If precision is negative, the integer part is rounded as well.
				@param method : number defining the rounding method to use.
											Currently are supported only
														0 for 'IEEE 754' rounding and
														1 for 'round half away from zero'.
											If another number is supplied, the result is undefined.
				@return String of the rounded number as returned by Lua function string.format().
												If one of the arguments is not a number, 'number' is returned unmodified.
]]
function FormatNum.round(source, precision, method)
				local number = tonumber(source);
				if (number and precision and method) then
								return numberToString(number, math.floor(precision), math.floor(method));
				end
				return source;
end -- FormatNum.round()
local p = { };
function p.format(frame)
				-- @param 1      : unformatted (but maybe rounded) floating point or integer number.
				-- @param number : same as 1 (DEPRECATED, backward compatibility).
				-- @param format : Formatting option. Currently there are
				--                 "at", "comma", "de", "dewiki", "de_currency", "ch", "ch_currency",
				--                 "en", "iso31_0", "iso31_0_point" and "pc" supported.
				local source = frame.args[1];
				if (not source) then
								source = frame.args.number;    -- DEPRECATED
								pcall( require, "Module:FormatNumDEPRECATED" )
				end
				return FormatNum.format(source, frame.args.format)  or  "";
end -- .format()
function p.round(frame)
				-- @param 1      : string with unformatted floating point or integer number.
				-- @param number : same as 1 (DEPRECATED, backward compatibility).
				-- @param precision : number of significant fractional part digits.
				--        If precision is negative, the integer part is rounded as well.
				-- @param method : number defining the rounding method to be used.
				--                 Currently are supported only
				--                    0 for 'IEEE 754' rounding and
				--                    1 for 'round half away from zero'.
				--                 If another number is supplied, the result is undefined.
				local precision = tonumber(frame.args.precision);
				local method = tonumber(frame.args.method);
				local source = frame.args[1];
				if (not source) then
								source = frame.args.number;    -- DEPRECATED
								pcall( require, "Module:FormatNumDEPRECATED" )
				end
				if (source and precision) then
								return FormatNum.round(source, precision, method);
				end
				return source or "";
end -- .round()
-- Export access for Lua modules
function p.FormatNum()
				return FormatNum;
end -- .FormatNum()
-- DEPRECATED
function p.formatNumber(frame)
				pcall( require, "Module:FormatNumDEPRECATED" )
				return p.format(frame);
end
function p.numberToString(frame)
				pcall( require, "Module:FormatNumDEPRECATED" )
				return p.round(frame);
end
return p;