Module:Convert: Difference between revisions

(update from sandbox per Template talk:Convert#Module version 7)
(update from sandbox per Template talk:Convert#Module version 8)
Line 17:
local numdot -- must be '.' or ',' or a character which works in a regex
local numsep, numsep_remove, numsep_remove2
local default_exceptions, link_exceptionsdata_code, all_units
local text_code
local varname -- can be a code to use variable names that depend on value
Line 143:
config = frame.args
maxsigfig = config.maxsigfig or 14 -- maximum number of significant figures
local data_module, text_module
-- Scribunto sets the global variable 'mw'.
local sandbox = config.sandbox and ('/' .. config.sandbox) or ''
-- A testing program can set the global variable 'is_test_run'.
local data_module, text_module,= "Module:Convert/data" .. data_codesandbox
text_module = "Module:Convert/text" .. sandbox
if is_test_run then
extra_module = "Module:Convert/extra" .. sandbox
local langcode = mw.language.getContentLanguage().code
spell_module = "Module:ConvertNumeric"
data_module = "convertdata-" .. langcode
text_module = "converttext-" .. langcode
extra_module = "convertextra-" .. langcode
spell_module = "ConvertNumeric"
else
local sandbox = config.sandbox and ('/' .. config.sandbox) or ''
data_module = "Module:Convert/data" .. sandbox
text_module = "Module:Convert/text" .. sandbox
extra_module = "Module:Convert/extra" .. sandbox
spell_module = "Module:ConvertNumeric"
end
data_code = mw.loadData(data_module)
text_code = mw.loadData(text_module)
default_exceptions = data_code.default_exceptions
link_exceptions = data_code.link_exceptions
all_units = data_code.all_units
local translation = text_code.translation_table
Line 554 ⟶ 542:
-- This is never called to determine a unit name or link because "per" units
-- are handled as a special case.
-- Similarly, the default output is handled elsewhere.
__index = function (self, key)
local value
Line 577 ⟶ 566:
end
}
 
local function make_per(unit_table, force_sp_us, ulookup)
-- Return true, t where t is a "per" unit with unit codes expanded to unit tables,
-- or return false, t where t is an error message table.
local result = { utype = unit_table.utype, per = {} }
override_from(result, unit_table, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })
result.symbol_raw = (result.symbol or false) -- to distinguish between a defined exception and a metatable calculation
local prefix
for i, v in ipairs(unit_table.per) do
if i == 1 and v == '' then
-- First unit symbol can be empty; that gives a nil first unit table.
elseif i == 1 and text_code.currency[v] then
prefix = currency_text or v
else
local success, t = ulookup(v)
if not success then return false, t end
result.per[i] = t
if t.sp_us then -- if the top or bottom unit forces sp=us, set the per unit to use the correct name/symbol
force_sp_us = true
end
end
end
local multiplier = unit_table.multiplier
if not result.utype then
-- Creating an automatic "per" unit.
local unit1 = result.per[1]
local utype = (unit1 and unit1.utype or prefix or '') .. '/' .. result.per[2].utype
local t = data_code.per_unit_fixups[utype]
if t then
if type(t) == 'table' then
utype = t.utype or utype
result.link = result.link or t.link
multiplier = multiplier or t.multiplier
else
utype = t
end
end
result.utype = utype
end
result.scalemultiplier = multiplier or 1
result.vprefix = prefix or false -- set to non-nil to avoid calling __index
result.sp_us = force_sp_us
return true, setmetatable(result, unit_per_mt)
end
 
local function lookup(unitcode, opt_sp_us, what, utable, fails, depth)
Line 632 ⟶ 665:
return true, result
end
if t.per then
local per = t.per -- nil/false, or a numbered table for "x/y" units
return make_per(t, force_sp_us, function (ucode) return lookup(ucode, opt_sp_us, 'no_combination', utable, fails, depth) end)
if per then
local result = { utype = t.utype, per = {} }
result.scalemultiplier = t.multiplier or 1
override_from(result, t, { 'invert', 'iscomplex', 'default', 'link', 'symbol', 'symlink' })
result.symbol_raw = (result.symbol or false) -- to distinguish between a defined exception and a metatable calculation
local cvt = result.per
local prefix
for i, v in ipairs(per) do
if i == 1 and text_code.currency[v] then
prefix = currency_text or v
else
local success, t = lookup(v, opt_sp_us, 'no_combination', utable, fails, depth)
if not success then return false, t end
cvt[i] = t
if t.sp_us then -- if the top or bottom unit forces sp=us, set the per unit to use the correct name/symbol
force_sp_us = true
end
end
end
if prefix then
result.vprefix = prefix
else
result.vprefix = false -- to avoid calling __index
end
result.sp_us = force_sp_us
return true, setmetatable(result, unit_per_mt)
end
local combo = t.combination -- nil or a table of unitcodes
Line 776 ⟶ 784:
if success or err_is_fatal then
return success, result
end
end
-- Look for x/y; split on right-most slash to get scale correct (x/y/z is x/y per z).
local top, bottom = unitcode:match('^(.-)/([^/]+)$')
if top and not unitcode:find('e%d') then
-- If valid, create an automatic "per" unit for an "x/y" unit code.
-- The unitcode must not include extraneous spaces.
-- Engineering notation (apart from at start and which has been stripped before here),
-- is not supported so do not make a per unit if find text like 'e3' in unitcode.
local success, result = make_per({ per = {top, bottom} }, opt_sp_us, function (ucode) return lookup(ucode, opt_sp_us, 'no_combination', utable, fails, depth) end)
if success then
return true, result
end
end
Line 1,003 ⟶ 1,023:
-- digits in local language
-- The given text is like '123' or '12345.6789' or '1.23e45'
-- (eat notationone cantime e-notation onlycould occur when processing an input value).,
-- but is now handled elsewhere for scientific notation).
-- The text has no sign (caller inserts that later, if necessary).
-- Separator is inserted only in the integer part of the significand
Line 1,029 ⟶ 1,050:
end
 
-- InputAn valuesinput can use valuesvalue like 1.23e12, butis aredisplayed neverusing displayedscientific notation (1.23×10¹²).
-- usingThat also makes the output use scientific notation, except for likesmall 1.23×10¹²values.
-- VeryIn addition, very small or very large output values use scientific notation.
-- Use format(fmtpower, significand, '10', exponent) where each argargument is a string.
local fmtpower = '%s<span style="margin:0 .15em 0 .25em">×</span>%s<sup>%s</sup>'
 
Line 1,038 ⟶ 1,059:
-- Return wikitext to display the implied value in scientific notation.
-- Input uses en digits; output uses digits in local language.
if #show > 1 then
show = show:sub(1, 1) .. '.' .. show:sub(2)
end
return format(fmtpower, from_en(show), from_en('10'), use_minus(from_en(tostring(exponent))))
end
Line 1,162 ⟶ 1,180:
-- * Uses a custom decimal mark, if wanted.
-- * Has digits grouped where necessary, if wanted.
-- * Uses scientific notation if requested, or for very small or large values
-- (which forces outputresult to not be spelled).
-- * Has no more than maxsigfig significant digits
-- (same as old template and {{#expr}}).
local xhi, xlo -- these control when scientific notation (exponent) is used
if parms.opt_scientific then
xhi, xlo = 4, 2 -- default for output if input uses e-notation
elseif parms.opt_scientific_always then
xhi, xlo = 0, 0 -- always use scientific notation (experimental)
else
xhi, xlo = 10, 4 -- default
end
local sign = isnegative and MINUS or ''
local maxlen = maxsigfig
Line 1,176 ⟶ 1,202:
if not tfrac and not exponent then
local integer, dot, decimals = show:match('^(%d*)(%.?)(.*)')
if #integer >== '0' or integer == 10'' then
show = integer .. decimals
exponent = #integer
elseif integer == '0' or integer == '' then
local zeros, figs = decimals:match('^(0*)([^0]?.*)')
if #figs == 0 then
Line 1,185 ⟶ 1,208:
show = '0.' .. zeros:sub(1, maxlen)
end
elseif #zeros >= 4xlo then
show = figs
exponent = -#zeros
Line 1,191 ⟶ 1,214:
show = '0.' .. zeros .. figs:sub(1, maxlen)
end
elseif #integer >= xhi then
show = integer .. decimals
exponent = #integer
else
maxlen = maxlen + #dot
Line 1,199 ⟶ 1,225:
end
if exponent then
local function zeros(n)
return string.rep('0', n)
end
if #show > maxlen then
show = show:sub(1, maxlen)
end
if exponent > 10xhi or exponent <= -4xlo or (exponent == 10xhi and show ~= '10000000001' .. zeros(xhi - 1)) then
-- When xhi, xlo = 10, 4 (the default), scientific notation is used if the
-- Rounded value satisfies: value >= 1e9 or value < 1e-4 (1e9 = 0.1e10).
-- rounded value satisfies: value >= 1e9 or value < 1e-4 (1e9 = 0.1e10),
-- except if show is '1000000000' (1e9), for example:
-- {{convert|1000000000|m|m|sigfig=10}} → 1,000,000,000 metres (1,000,000,000 m)
local significand
if #show > 1 then
significand = show:sub(1, 1) .. '.' .. show:sub(2)
else
significand = show
end
return {
clean = '.' .. show,
exponent = exponent,
sign = sign,
show = sign .. with_exponent(showsignificand, exponent-1),
is_scientific = true,
}
end
if exponent >= #show then
show = show .. string.repzeros('0', exponent - #show) -- result has no dot
elseif exponent <= 0 then
show = '0.' .. string.repzeros('0', -exponent) .. show
else
show = show:sub(1, exponent) .. '.' .. show:sub(exponent+1)
Line 1,261 ⟶ 1,299:
-- x (if present) is an integer or has a single digit after decimal mark
-- y and z are unsigned integers
-- e -notation is not accepted
-- The overall number can start with '+' or '-' (so '12+3/4' and '+12+3/4'
-- and '-12-3/4' are valid).
Line 1,295 ⟶ 1,333:
-- '12.3+' and '12.3-' are also accepted (single digit after decimal point)
-- because '12.3+1/2 hands' is valid (12 hands 3½ inches).
local num1, num2, frac_sign = prefix:match('^(%d+)(%.?%d?)%s*([+%-])$')
if num1 == nil then return nil end
if num2 == '' then -- num2 must be '' or like '.1' but not '.' or '.12'
Line 1,400 ⟶ 1,438:
end
if show == nil then
-- clean is a non-empty string with no spaces, and does not represent a fraction,
-- and tonumber(clean) is a number.
-- If the input uses e-notation, show will be displayed using a power of ten, but
-- we use the number as given so it might not be normalized scientific notation.
-- The input value is spelled if specified so any e-notation is ignored;
-- that allows input like 2e6 to be spelled as "two million" which works
-- because the spell module converts '2e6' to '2000000' before spelling.
local function rounded(value)
local precision = parms.input_precision
if precision and 0 <= precision and precision <= 8 then
local fmt = '%.' .. format('%d', precision) .. 'f'
return fmt:format(value + 2e-14) -- fudge for some common cases of bad rounding
end
end
singular = (value == 1)
local scientific
local precision = parms.input_precision
local significand, exponent = clean:match('^([%d.]+)[Ee]([+%-]?%d+)')
if precision and 0 <= precision and precision <= 8 then
if significand then
local fmt = '%.' .. format('%d', precision) .. 'f'
show = with_exponent(rounded(tonumber(significand)) or significand, exponent)
show = fmt:format(value + 2e-14) -- fudge for some common cases of bad rounding
scientific = true
else
show = with_separator(parms, rounded(value) or clean)
end
show = propersign .. with_separator(parms, show)
if parms.opt_spell_in then
show = spell_number(parms, 'in', propersign .. clean) or show
scientific = false
end
if scientific then
parms.opt_scientific = true
end
end
Line 1,811 ⟶ 1,868:
parms.joins = disp_joins[disp] or default_joins
parms.join_between = parms.joins[3] or parms.join_between
parms.wantname = parms.joins.wantname
end
if (en_default and not parms.opt_lang_local and (parms[1] or ''):find('%d')) or parms.opt_lang_en then
Line 2,496 ⟶ 2,554:
-- 'smallsuffix' if (value < 120), or 'bigsuffix' otherwise.
-- Input must use en digits and '.' decimal mark.
local default = data_code.default_exceptions[unit_table.defkey or unit_table.symbol] or unit_table.default
if not default then
local per = unit_table.per
if per then
local function a_default(v, u)
local success, ucode = get_default(v, u)
if not success then
return '?' -- an unlikely error has occurred; will cause lookup of default to fail
end
-- Attempt to use only the first unit if a combination or output multiple.
-- This is not bulletproof but should work for most cases.
-- Where it does not work, the convert will need to specify the wanted output unit.
local t = all_units[ucode]
if t then
local combo = t.combination
if combo then
-- For a multiple like ftin, the "first" unit (ft) is last in the combination.
local i = t.multiple and #t.combination or 1
ucode = combo[i]
end
end
return ucode
end
local unit1, unit2 = per[1], per[2]
local def1 = (unit1 and a_default(value, unit1) or unit_table.vprefix or '')
local def2 = a_default(1, unit2) -- 1 because per unit of denominator
return true, def1 .. '/' .. def2
end
return false, { 'cvt_no_default', unit_table.symbol }
end
Line 2,637 ⟶ 2,721:
if want_link and unit_table.link then
if abbr_on or not varname then
result = (unit1 and linked_id(unit1[, key_id], false, clean) or '') .. result .. linked_id(unit2[, key_id2], false, '1')
else
result = (unit1 and variable_name(clean, unit1) or '') .. result .. variable_name('1', unit2)
Line 2,677 ⟶ 2,761:
end
if want_link then
local link = data_code.link_exceptions[unit_table.linkey or unit_table.symbol] or unit_table.link
if link then
local before = ''
Line 2,745 ⟶ 2,829:
else
if abbr_org == nil then
if parms.wantname then
if disp == 'br' or disp == 'or' or disp == 'slash' then
want_name = true
end
Anonymous user