Zum Inhalt springen

Modul:FormatNum

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 18. Juli 2013 um 16:45 Uhr durch TMg (Diskussion | Beiträge) (Nicht nur bei fehlendem/undefiniertem Parameter frame.args.format auf den Standard zurück fallen, sondern auch, wenn er leer ist, denn ein Format mit dem Namen "" kann es ja niemals geben). Sie kann sich erheblich von der aktuellen Version unterscheiden.
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.
Versionsbezeichnung auf WikiData: 2020-08-06

--[[ 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"
-- (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 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 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 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;

-- 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;

-- Format table for "dewiki" (only for convenience, same as "de_currency").
FORMAT_TABLE.dewiki = 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 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;