Module:Formatnum

-- This module formats numbers local p = {}

local digit = { -- substitution of decimal digits for languages not supported by mw.language:formatNum in core Lua libraries for MediaWiki ["ml-old"] = { '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯' }, ["mn"]    = { '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'}, ["ta"]    = { '௦', '௧', '௨', '௩', '௪', '௫', '௬', '௭', '௮', '௯'}, ["te"]    = { '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'}, ["th"]    = { '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'} }

function p.main(frame) local args = require("Dev:Arguments").getArgs(frame) local prec   = args.prec or '' local sep    = args.sep or '' local number = args[1] or args.number or '' local lang   = args[2] or args.lang or '' -- validate the language parameter within MediaWiki's caller frame if lang == "arabic-indic" then -- only for compatibility ("arabic-indic" is not a SupportedLanguage) lang = "fa" -- better support than "ks" elseif lang == '' or (not isKnownLanguageTag(lang) and not digit.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 = 'en' end return p.formatNum(number, lang, prec, sep ~= '') end

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, '[Ee]') 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 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' .. mw.ustring.gsub(language:formatNum(tonumber(exponent)), groupsep, '') 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[lang] then for i, v in ipairs(digit[lang]) do           number = mw.ustring.gsub(number, tostring(i - 1), v)        end end

return number end

function isKnownLanguageTag(langCode) local bool if type(langCode) =="number" then return false end bool = (langCode and type(langCode)~="number" and mw.language.isValidBuiltInCode( langCode ) and        mw.language.fetchLanguageName( langCode )) ~= "" return bool end

return p