Difference between revisions of "Module:FormatNumber"
Jump to navigation
Jump to search
m (Add a thin space between number and number suffix for clarity) |
(Fix sorting for infinity values, add sortkey parameter) |
||
Line 2: | Line 2: | ||
Module:Num | Module:Num | ||
For displaying large numbers in a consistent format | For displaying large numbers in a consistent format | ||
− | + | See Template:Number | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
----------------------------------------------------------------------------]]-- | ----------------------------------------------------------------------------]]-- | ||
local p = {} | local p = {} | ||
Line 58: | Line 52: | ||
end | end | ||
− | -- Get the number for use in data-sort-value attributes | + | -- Get the number for use in data-sort-value attributes (for table sorting) |
local function getSortValue(value) | local function getSortValue(value) | ||
− | if belowThreshold(value) then return string.format('%d', value) | + | if belowThreshold(value) then |
− | return string.format('%e', value) | + | return string.format('%d', value) |
+ | elseif value == -math.huge then | ||
+ | return '-2e+308' -- lower than the lowest possible non-inf number | ||
+ | elseif value == math.huge then | ||
+ | return '2e+308' -- higher than the highest possible non-inf number | ||
+ | else | ||
+ | return string.format('%e', value) | ||
+ | end | ||
end | end | ||
Line 136: | Line 137: | ||
local prefix = frame.args.prefix or frame:getParent().args.prefix | local prefix = frame.args.prefix or frame:getParent().args.prefix | ||
local suffix = frame.args.suffix or frame:getParent().args.suffix | local suffix = frame.args.suffix or frame:getParent().args.suffix | ||
+ | local sortkey = frame.args.sortkey or frame:getParent().args.sortkey | ||
if not rawNum or rawNum == '' then error('Number is required') end | if not rawNum or rawNum == '' then error('Number is required') end | ||
local num = getNumber(rawNum) | local num = getNumber(rawNum) | ||
Line 141: | Line 143: | ||
return mw.html.create('span') | return mw.html.create('span') | ||
:wikitext(p.format(num, prefix, suffix, format)) | :wikitext(p.format(num, prefix, suffix, format)) | ||
− | :attr('data-sort-value', getSortValue(num)) | + | :attr('data-sort-value', sortkey or getSortValue(num)) |
end | end | ||
return p | return p |
Revision as of 23:11, 19 March 2021
Documentation for this module may be created at Module:FormatNumber/doc
--[[----------------------------------------------------------------------------
Module:Num
For displaying large numbers in a consistent format
See Template:Number
----------------------------------------------------------------------------]]--
local p = {}
local THRESHOLD = 1e6 -- Anything below this number is displayed in long format
local LANG = mw.language.getContentLanguage()
local suffixes = {
't', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', 'No', 'De',
'UDe', 'DDe', 'TDe', 'QaD', 'QiD', 'SxD', 'SpD', 'OcD', 'NoD',
'Vi', 'UVi', 'DVi', 'TVi', 'QaV', 'QiV', 'SxV', 'SpV', 'OcV', 'NoV',
'Tr', 'UTr', 'DTr', 'TTr', 'QaT', 'QiT', 'SxT', 'SpT', 'OcT', 'NoT',
'Qua', 'UQu', 'DQu', 'TQu', 'QQu', 'QiQ', 'SxQ', 'SpQ', 'OcQ', 'NoQ',
'Qui', 'UQi', 'DQi', 'TQi', 'QQi', 'QiQi', 'SxQi', 'SpQi', 'OcQi', 'NoQi',
'Sg', 'USg', 'DSg', 'TSg', 'QSg', 'QiS', 'SxS', 'SpS', 'OcS', 'NoS',
'Spt', 'USp', 'DSp', 'TSp', 'QSp', 'QiSp', 'SxSp', 'SpSp', 'OcSp', 'NoSp',
'Og', 'UOg', 'DOg', 'TOg', 'QOg', 'QiO', 'SxO', 'SpO', 'OcO', 'NoO',
'Ng', 'UNg', 'DNg', 'TNg', 'QNg', 'QiNg', 'SxN', 'SpN', 'OcN', 'NoN',
'Ce', 'UCe', 'DCe'
}
-- Round a value to X decimal places
local function round(value, decimals)
local p = 10^(decimals or 3)
local n = value * p
return (value >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)) / p
end
local function belowThreshold(value)
return (value > -THRESHOLD and value < THRESHOLD)
end
-- Ensure a number is a valid number and return it
local function getNumber(value)
local num
if value == '-inf' or value == '-Infinity' then
num = -math.huge
elseif value == 'inf' or value == 'Infinity' then
num = math.huge
else
-- use this instead of tonumber() so we can parse thousand seperators
num = LANG:parseFormattedNumber(value)
end
if not num or num == nil then
error(string.format('"%s" is not a valid number', value))
end
return num
end
-- Get the number for use in data-sort-value attributes (for table sorting)
local function getSortValue(value)
if belowThreshold(value) then
return string.format('%d', value)
elseif value == -math.huge then
return '-2e+308' -- lower than the lowest possible non-inf number
elseif value == math.huge then
return '2e+308' -- higher than the highest possible non-inf number
else
return string.format('%e', value)
end
end
-- Get a number in scientific notation, ingame style (e.g. 12e7)
local function getScientific(value, prefix, suffix)
-- Do this manually rather than using string.format "%e" representation,
-- so that we can match output to ingame notation
local exponent = math.floor(math.log10(math.abs(value)))
local mantissa = round(value / (10^exponent), 3)
return string.format('%s%se%s%s',
prefix or '',
mantissa,
exponent,
suffix or ''
)
end
-- Get a number with its number suffix (e.g. 120 M)
local function getNamed(value, prefix, suffix)
local exponent = math.floor(math.log10(math.abs(value)))
local remainder = exponent % 3
return string.format(
'%s%s %s%s',
prefix or '',
round(value / (10 ^ (exponent - remainder)), 3), -- number
suffixes[math.floor(exponent / 3)], -- number suffix
suffix or ''
)
end
-- Choose a number representation depending on the size of the number
-- Second parameter forces format: 'named' or 'scientific'
-- For modules only - do not call this from template, use 'main' instead.
-- No type checking is performed on 'value' - this is the responsibility
-- of the calling function
function p.format(value, prefix, suffix, format)
if value == -math.huge then
return string.format('%s-Infinity%s', prefix or '', suffix or '')
elseif value == math.huge then
return string.format('%sInfinity%s', prefix or '', suffix or '')
elseif belowThreshold(value) then
return string.format('%s%s%s',
prefix or '',
LANG:formatNum(value),
suffix or ''
)
elseif format == 'named' then
return string.format(
'<abbr title="%s">%s</abbr>',
getScientific(value, prefix, suffix),
getNamed(value, prefix, suffix)
)
elseif format == 'scientific' then
return string.format(
'<abbr title="%s">%s</abbr>',
getNamed(value, prefix, suffix),
getScientific(value, prefix, suffix)
)
else
return string.format(
'%s \'\'(%s)\'\'',
getNamed(value, prefix, suffix),
getScientific(value, prefix, suffix)
)
end
end
-- Use this function when calling from template
-- See usage above
function p.main(frame)
if not frame then error('No frame found') end
local rawNum = frame.args[1] or frame:getParent().args[1]
local format = frame.args.format or frame:getParent().args.format
local prefix = frame.args.prefix or frame:getParent().args.prefix
local suffix = frame.args.suffix or frame:getParent().args.suffix
local sortkey = frame.args.sortkey or frame:getParent().args.sortkey
if not rawNum or rawNum == '' then error('Number is required') end
local num = getNumber(rawNum)
return mw.html.create('span')
:wikitext(p.format(num, prefix, suffix, format))
:attr('data-sort-value', sortkey or getSortValue(num))
end
return p