Modul:FormatNum
Erscheinungsbild
Vorlagenprogrammierung | Diskussionen | Lua | Test | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData:
2020-08-06
--[[
Main table - used by invoke.
Exported functions: numberToString, formatNumber
]]
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"
(German: "kaufmaennisches Runden"), 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 "de".
]]
FORMAT_TABLE.de = {};
FORMAT_TABLE.de.decimalMark = ",";
FORMAT_TABLE.de.groupMark = " ";
FORMAT_TABLE.de.groupMinLength = 5;
FORMAT_TABLE.de.groupOnlyIntegerPart = false;
FORMAT_TABLE.de.usingSpaces = true;
--[[
Format table for "de_currency".
]]
FORMAT_TABLE.de_currency = {};
FORMAT_TABLE.de_currency.decimalMark = ",";
FORMAT_TABLE.de_currency.groupMark = ".";
FORMAT_TABLE.de_currency.groupMinLength = 5;
FORMAT_TABLE.de_currency.groupOnlyIntegerPart = true;
FORMAT_TABLE.de_currency.usingSpaces = false;
--[[
Format table for "ch".
]]
FORMAT_TABLE.ch = {};
FORMAT_TABLE.ch.decimalMark = ",";
FORMAT_TABLE.ch.groupMark = "'";
FORMAT_TABLE.ch.groupMinLength = 5;
FORMAT_TABLE.ch.groupOnlyIntegerPart = true;
FORMAT_TABLE.ch.usingSpaces = false;
--[[
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.en.usingSpaces = false;
--[[
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.iso31_0.usingSpaces = true;
--[[
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.iso31_0_point.usingSpaces = true;
--[[
Format table for "pc" (must be nil to prevent formatting).
]]
FORMAT_TABLE.pc = nil;
--[[
Format table for "at" (only for convenience, same as "iso31_0").
]]
FORMAT_TABLE.at = FORMAT_TABLE.iso31_0;
--[[
Format table for "ch_currency" (only for convenience, same as "de_currency").
]]
FORMAT_TABLE.ch_currency = FORMAT_TABLE.de_currency;
--[[
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 internalNumberToString(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
--[[
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 internalFormatNumber(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
--[[
Exported function for rounding numbers.
@param a_frame : Frame holding the arguments from the invoke call.
@argument number : String representation of an unformatted floating point
or integer number.
@argument precision : String representation of the number of significant
fractional part digits. If it is negative, the integer part is rounded
as well.
@argument method : String representation of the number defining the
rounding method to use. Currently only "0" for 'IEEE 754' rounding and
"1" for 'round half away from zero' are supported. If another number is
supplied, the result is undefined.
@return String of the rounded number like returned by Lua function
string.format(). If one of the arguments is not a number, the argument
'number' is returned unmodified.
]]
function formatNum.numberToString(a_frame)
local number = tonumber(a_frame.args["number"]);
local precision = tonumber(a_frame.args["precision"]);
local method = tonumber(a_frame.args["method"]);
if (number and precision and method) then
return internalNumberToString(number, math.floor(precision), math.floor(method));
end
return a_frame.args["number"];
end
--[[
Exported function for formatting numbers.
@param a_frame : Frame holding the arguments from the invoke call.
@argument number : String representation of an unformatted (but maybe rounded)
floating point or integer number.
@argument format : String defining the formatting options. Currently there are
"at", "de", "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 'format' is invalid or
'number' is not a valid string representation of a number, the argument
'number' is returned unmodified.
]]
function formatNum.formatNumber(a_frame)
local number = a_frame.args["number"];
local format = a_frame.args["format"];
if (number and format) then
format = FORMAT_TABLE[format];
if (tonumber(number) and format) then
-- lua can parse the number (first check passed) and 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(number, "^%d+%.%d+$"))) then
-- number has valid format (only digits or digits.digits) -> format it and add sign (if any) again
number = sign .. internalFormatNumber(number, format.decimalMark, format.groupMark, format.groupMinLength, format.groupOnlyIntegerPart);
if (format.usingSpaces) then
-- prevent line breaks when using spaces
number = "<span style=\"white-space:nowrap\">" .. number .. "</span>";
end
else
-- number has no valid format -> undo all modifications
number = a_frame.args["number"];
end
end
end
return number;
end
return formatNum;