Misplaced Pages

Module:Formatnum: Difference between revisions

Article snapshot taken from Wikipedia with creative commons attribution-sharealike license. Give it a read and then ask your questions in the chat. We can research this topic together.
Browse history interactively← Previous editNext edit →Content deleted Content added
Revision as of 18:00, 19 February 2021 view sourceTrigenibinion (talk | contribs)Extended confirmed users20,472 edits Add wordifyTag: Reverted← Previous edit Revision as of 18:37, 19 February 2021 view source Trigenibinion (talk | contribs)Extended confirmed users20,472 edits FixTag: RevertedNext edit →
Line 151: Line 151:


function p.wordify(frame) function p.wordify(frame)
local args = frame:getParent().args local args = frame.args
if not args and not args.number then if not args and not args.number then
args = frame.args args = frame.args

Revision as of 18:37, 19 February 2021

Module documentation[view] [edit] [history] [purge]

This module provides a number formatting function. This function can be used from #invoke or from other Lua modules.

This module is used by Module:Complex date

Use from other Lua modules

To use the module from normal wiki pages, no special preparation is needed. If you are using the module from another Lua module, first you need to load it, like this:

local mf = require('Module:Formatnum')

(The mf variable stands for Module Formatnum; you can choose something more descriptive if you prefer.)

Most functions in the module have a version for Lua and a version for #invoke. It is possible to use the #invoke functions from other Lua modules, but using the Lua functions has the advantage that you do not need to access a Lua frame object. Lua functions are preceded by _, whereas #invoke functions are not.

main

{{#invoke:Formatnum|main|x|lang=|prec=|sep=}}
mf.formatNum(x, lang, prec, sep)

See also

Math templates
  • Functions
  • Numeral systems
Functions
Numeral systems
Conversions
convert many units (see: list)
cvt abbreviated {{convert}}
convinfobox {{convert}} for infoboxes
bbl to t barrels of oil to tonnes
long ton long hundredweights, quarters and pounds to kilograms;
long tons and hundredweights to pounds and metric tons
miles-chains miles and chains to kilometres linking "chains"
decdeg degrees, minutes, and seconds to decimal degrees
deg2dms decimal degrees to degrees, minutes, and seconds
deg2hms decimal degrees to hour angle (in hours, minutes, and seconds)
hms2deg hour angle (in hours, minutes, and seconds) to decimal degrees
inflation calculate inflation of Consumer Price Index-related prices
pop density population density in an area
track gauge railway track gauges
Notation and formatting
bigmath bigger font to match TeX \displaystyle (standalone formulas only)
bra–ket notation
ceil, floor calculations :mw:Help:#expr; formatting indicators ⌈3.14⌉, ⌊3.14⌋ (no calculation performed)
fraction slant fractions 3⁄5 (not for maths/science articles; use standing or upright fractions {{sfrac}} instead)
intmath integral symbols
  • langle
  • rangle
  • angbr
  • angular brackets
  • ldelim
  • rdelim
  • multiline delimiters (2–5 lines inclusive)
    abs absolute values (paired vertical lines)
    math short text-based formulas
    mathcal calligraphic font; alternative to LaTeX \mathcal{...}
    mvar individual italicized maths variables in normal text
  • overline
  • underline
  • a line set above/below a sequence of characters
    overarc an arc set above a sequence of characters
  • overset
  • underset
  • arbitrary characters/diacritics set above/below one another
    pars parentheses that can be resized (∑)
    sfrac "standing" or upright fractions ⁠3/5⁠ (use in maths/science articles instead of{{fraction}})
  • sub
  • sup
  • su
  • subscripts and superscripts
    tmath Wrap TeX in <math> tags
    tombstone symbol indicating the end of a proof
    val measurement values, uncertainties and units
    vec various overarrows, underarrows, etc.
  • Boxes
  • Tags
  • Notices
  • BoxesTags
    The above documentation is transcluded from Module:Formatnum/doc. (edit | history)
    Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
    Subpages of this module.

    -- This module is intended to replace the functionality of Template:Formatnum and related templates. 
    local mm = require('Module:Math')
    
    local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.
    
    --[[
    Helper functions used to avoid redundant code.
    ]]
    
    local function err(msg)
    	-- Generates wikitext error messages.
    	return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg)
    end
    
    function p.main(frame)
        local args = frame:getParent().args
        if not args and not args.number then
            args = frame.args
        end
        local prec    = args.prec or ''
        local sep     = args.sep or ''
        local number  = args or args.number or ''
        local lang    = args or args.lang or ''
        -- validate the language parameter within MediaWiki's caller frame
        if lang:lower() == "none" then
            -- no language, so do nothing
        elseif lang == "arabic-indic" then -- only for back-compatibility ("arabic-indic" is not a SupportedLanguage)
            lang = "fa" -- better support than "ks"
        elseif lang == '' or not mw.language.isSupportedLanguage(lang) then
            -- Note that 'SupportedLanguages' are not necessarily 'BuiltinValidCodes', and so they are not necessarily
            -- 'KnownLanguages' (with a language name defined at least in the default localisation of the local wiki).
            -- But they all are ValidLanguageCodes (suitable as Wiki subpages or identifiers: no slash, colon, HTML tags, or entities)
            -- In addition, they do not contain any capital letter in order to be unique in page titles (restriction inexistant in BCP47),
            -- but they may violate the standard format of BCP47 language tags for specific needs in MediaWiki.
            -- Empty/unspecified and unsupported languages are treated here in Commons using the user's language,
            -- instead of the local 'ContentLanguage' of the Wiki.
            lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language
            if not mw.language.isSupportedLanguage(lang) then
                lang = mw.language.getContentLanguage().code
            end
        end
        return p.formatNum(number, lang, prec, sep ~= '')
    end
    
    local digit = { -- substitution of decimal digits for languages not supported by mw.language:formatNum() in core Lua libraries for MediaWiki
         = { '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯' },
             = { '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'},
             = { '௦', '௧', '௨', '௩', '௪', '௫', '௬', '௭', '௮', '௯'},
             = { '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'},
             = { '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'}
    }
    
    function p.formatNum(number, lang, prec, compact)
        -- Do not alter the specified value when it is not a valid number, return it as is
        local value = tonumber(number)
        if value == nil then
            return number
        end
        -- Basic ASCII-only formatting (without paddings)
        number = tostring(value)
    
        -- Check the presence of an exponent (incorrectly managed in mw.language:FormatNum() and even forgotten due to an internal bug, e.g. in Hindi)
        local exponent
        local pos = string.find(number, '')
        if pos ~= nil then
            exponent = string.sub(number, pos + 1, string.len(number))
            number = string.sub(number, 1, pos - 1)
        else
            exponent = ''
        end
    
        -- Check the minimum precision requested
        prec = tonumber(prec) -- nil if not specified as a true number
        if prec ~= nil then
            prec = math.floor(prec)
            if prec < 0 then
                prec = nil -- discard an incorrect precision (not a positive integer)
            elseif prec > 14 then
                prec = 14 -- maximum precision supported by tostring(number)
            end
        end
    
        -- Preprocess the minimum precision in the ASCII string
        local dot
        if (prec or 0) > 0 then
            pos = string.find(number, '.', 1, true) -- plain search, no regexp
            if pos ~= nil then
                prec = pos + prec - string.len(number) -- effective number of trailing decimals to add or remove
                dot = '' -- already present
            else
                dot = '.' -- must be added
            end
        else
            dot = '' -- don't add dot
            prec = 0 -- don't alter the precision
        end
        
        if lang ~= nil and mw.language.isKnownLanguageTag(lang) == true then
            -- Convert number to localized digits, decimal separator, and group separators
            local language = mw.getLanguage(lang)
            if compact then
                number = language:formatNum(tonumber(number), { noCommafy = 'y' }) -- caveat: can load localized resources for up to 20 languages
            else
                number = language:formatNum(tonumber(number)) -- caveat: can load localized resources for up to 20 languages
            end
            -- Postprocessing the precision
            if prec > 0 then
                local zero = language:formatNum(0)
                number = number .. dot .. mw.ustring.rep(zero, prec)
            elseif prec < 0 then
                -- TODO: rounding of last decimal; here only truncate decimals in excess
                number = mw.ustring.sub(number, 1, mw.ustring.len(number) + prec)
            end
            -- Append the localized base-10 exponent without grouping separators (there's no reliable way to detect a localized leading symbol 'E')
            if exponent ~= '' then
                number = number .. 'E' .. language:formatNum(tonumber(exponent),{noCommafy=true})
            end
        else -- not localized, ASCII only
            -- Postprocessing the precision
            if prec > 0 then
                number = number .. dot .. mw.string.rep('0', prec)
            elseif prec < 0 then
                -- TODO: rounding of last decimal; here only truncate decimals in excess
                number = mw.string.sub(number, 1, mw.string.len(number) + prec)
            end
            -- Append the base-10 exponent
            if exponent ~= '' then
                number = number .. 'E' .. exponent
            end
        end
    
        -- Special cases for substitution of ASCII digits (missing support in Lua core libraries for some languages)
        if digit then
            for i, v in ipairs(digit) do
                number = mw.ustring.gsub(number, tostring(i - 1), v)
            end
        end
    
        return number
    end
    
    --[[
    mod
    
    Implements the division operator
    
    Usage:
    {{#invoke:Formatnum | wordify | x | y | round= | precision= }}
    
    --]]
    
    function p.wordify(frame)
    	local args = frame.args
        if not args and not args.number then
            args = frame.args
        end
    	local x = args
    	local numsys = args.numsys
    	local prec =  args.prec
    	local lk = args.lk
    	return p._wordify(x, numsys, prec, (lk == "on" and true or false))
    end
    
    function p._wordify(x, numsys, prec, lk)
    	if tonumber(x) then
    		if numsys == "usa" or numsys == nil or numsys == "" then
    			if x / 1E12 >= 1 then
    				return p.formatNum(mm._round(x / 1E12, prec), "en") .. " " .. (lk and "]" or "trillion")
    			elseif x / 1E9 >= 1 then
    				return p.formatNum(mm._round(x / 1E9, prec), "en") .. " " .. (lk and "]]" or "billion")
    			elseif x / 1E6 >= 1 then
    				return p.formatNum(mm._round(x / 1E6, prec), "en") .. " million"
    			else
    				return p.formatNum(mm._round(x, prec), "en") 
    			end
    		elseif numsys == "ind" then
    			if x / 1E12 >= 1 then
    				return p.formatNum(mm._round(x / 1E12, prec), "en") .. " " .. (lk and "] ]" or "lakh crore")
    			elseif x / 1E7 >= 1 then
    				return p.formatNum(mm._round(x / 1E7, prec), "en") .. " " .. (lk and "]" or "crore")
    			elseif x / 1E5 >= 1 then
    				return p.formatNum(mm._round(x / 1E5, prec), "en") .. " " .. (lk and "]" or "lakh")
    			else
    				return p.formatNum(mm._round(x, prec), "en") 
    			end
    		else
    			return err("number system not supported")
    		end	
    	else
    	  return err("Not a number: " .. x)
    	end
    end
    
    --[[
    Helper function that interprets the input numerically.  If the
    input does not appear to be a number, attempts evaluating it as
    a parser functions expression.
    ]]
    
    function p._cleanNumber(number_string)
    	if type(number_string) == 'number' then
    		-- We were passed a number, so we don't need to do any processing.
    		return number_string, tostring(number_string)
    	elseif type(number_string) ~= 'string' or not number_string:find('%S') then
    		-- We were passed a non-string or a blank string, so exit.
    		return nil, nil;
    	end
    
    	-- Attempt basic conversion
    	local number = tonumber(number_string)
    
    	-- If failed, attempt to evaluate input as an expression
    	if number == nil then
    		local success, result = pcall(mw.ext.ParserFunctions.expr, number_string)
    		if success then
    			number = tonumber(result)
    			number_string = tostring(number)
    		else
    			number = nil
    			number_string = nil
    		end
    	else
    		number_string = number_string:match("^%s*(.-)%s*$") -- String is valid but may contain padding, clean it.
    		number_string = number_string:match("^%+(.*)$") or number_string -- Trim any leading + signs.
    		if number_string:find('^%-?0') then
    			-- Number is using 0xnnn notation to indicate base 16; use the number that Lua detected instead.
    			number_string = tostring(number)
    		end
    	end
    
    	return number, number_string
    end
    
    return p