-- Each string is a formatted number in en digits and optional '.' decimal mark.
-- A table represents a fraction: integer, numerator, denominator;
-- if a table is given, exponent must be nil.
-- Return t where t is a table with fields:
-- show = wikitext formatted to display implied value
-- (digits in local language)
-- is_scientific = true if show uses scientific notation
-- clean = unformatted show (possibly adjusted and with inserted '.')
-- (en digits)
-- sign = '' or MINUS
-- exponent = exponent (possibly adjusted)
-- The clean and exponent fields can be used to calculate the
-- rounded absolute value, if needed.
---
-- The value implied by the arguments is found from:
-- exponent is nil; and
-- show is a string of digits (no sign), with an optional dot;
-- show = '123.4' is value 123.4, '1234' is value 1234.0;
-- or:
-- exponent is an integer indicating where dot should be;
-- show is a string of digits (no sign and no dot);
-- there is an implied dot before show;
-- show does not start with '0';
-- show = '1234', exponent = 3 is value 0.1234*10^3 = 123.4.
---
-- The formatted result:
-- * Is for an output value and is spelled if wanted and possible.
-- * Includes a Unicode minus if isnegative and not spelled.
-- * 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 result to not be spelled).
-- * Has no more than maxsigfig significant digits
-- (same as old template and ).
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
local tfrac
if type(show) == 'table' then
tfrac = show
show = tfrac.wholestr
assert(exponent == nil, 'Bug: exponent given with fraction')
end
if not tfrac and not exponent then
local integer, dot, decimals = show:match('^(%d*)(%.?)(.*)')
if integer == '0' or integer == '' then
local zeros, figs = decimals:match('^(0*)([^0]?.*)')
if #figs == 0 then
if #zeros > maxlen then
show = '0.' .. zeros:sub(1, maxlen)
end
elseif #zeros >= xlo then
show = figs
exponent = -#zeros
elseif #figs > maxlen then
show = '0.' .. zeros .. figs:sub(1, maxlen)
end
elseif #integer >= xhi then
show = integer .. decimals
exponent = #integer
else
maxlen = maxlen + #dot
if #show > maxlen then
show = show:sub(1, maxlen)
end
end
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 > xhi or exponent <= -xlo or (exponent == xhi and show ~= '1' .. 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),
-- except if show is '1000000000' (1e9), for example:
-- → 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(parms, significand, exponent-1),
is_scientific = true,
}
end
if exponent >= #show then
show = show .. zeros(exponent - #show) -- result has no dot
elseif exponent <= 0 then
show = '0.' .. zeros(-exponent) .. show
else
show = show:sub(1, exponent) .. '.' .. show:sub(exponent+1)
end
end
local formatted_show
if tfrac then
show = tostring(tfrac.value) -- to set clean in returned table
formatted_show = format_fraction(parms, 'out', isnegative, tfrac.wholestr, tfrac.numstr, tfrac.denstr, parms.opt_spell_out)
else
if isnegative and show:match('^0.?0*$') then
sign = '' -- don't show minus if result is negative but rounds to zero
end
formatted_show = sign .. with_separator(parms, show)
if parms.opt_spell_out then
formatted_show = spell_number(parms, 'out', sign .. show) or formatted_show
end
end
return {
clean = show,
sign = sign,
show = formatted_show,
is_scientific = false, -- to avoid calling __index
}
end  
local function extract_fraction(parms, text, negative)
-- If text represents a fraction, return
-- value, altvalue, show, denominator
-- where
-- value is a number (value of the fraction in argument text)
-- altvalue is an alternate interpretation of any fraction for the hands
-- unit where "12.1+3/4" means 12 hands 1.75 inches
-- show is a string (formatted text for display of an input value,
-- and is spelled if wanted and possible)
-- denominator is value of the denominator in the fraction
-- Otherwise, return nil.
-- Input uses en digits and '.' decimal mark (input has been translated).
-- Output uses digits in local language and local decimal mark, if any.
---
-- Originally this function accepted x+y/z where x, y, z were any valid
-- numbers, possibly with a sign. For example '1.23e+2+1.2/2.4' = 123.5,
-- and '2-3/8' = 1.625. However, such usages were found to be errors or
-- misunderstandings, so since August 2014 the following restrictions apply:
-- 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).
-- Any leading negative sign is removed by the caller, so only inputs
-- like the following are accepted here (may have whitespace):
-- negative = false false true (there was a leading '-')
-- text = '2/3' '+2/3' '2/3'
-- text = '1+2/3' '+1+2/3' '1-2/3'
-- text = '12.3+1/2' '+12.3+1/2' '12.3-1/2'
-- Values like '12.3+1/2' are accepted, but are intended only for use
-- with the hands unit (not worth adding code to enforce that).
---
local leading_plus, prefix, numstr, slashes, denstr =
text:match('^%s*(%+?)%s*(.-)%s*(%d+)%s*(/+)%s*(%d+)%s*$')
if not leading_plus then
-- Accept a single U+2044 fraction slash because that may be pasted.
leading_plus, prefix, numstr, denstr =
text:match('^%s*(%+?)%s*(.-)%s*(%d+)%s*⁄%s*(%d+)%s*$')
slashes = '/'
end
local numerator = tonumber(numstr)
local denominator = tonumber(denstr)
if numerator == nil or denominator == nil or (negative and leading_plus ~= '') then
return nil
end
local whole, wholestr
if prefix == '' then
wholestr = ''
whole = 0
else
-- Any prefix must be like '12+' or '12-' (whole number and fraction sign);
-- '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'
wholestr = num1
else
if #num2 ~= 2 then return nil end
wholestr = num1 .. num2
end
if frac_sign ~= (negative and '-' or '+') then return nil end
whole = tonumber(wholestr)
if whole == nil then return nil end
end
local value = whole + numerator / denominator
if not valid_number(value) then return nil end
local altvalue = whole + numerator / (denominator * 10)
local style = #slashes -- kludge: 1 or 2 slashes can be used to select style
if style > 2 then style = 2 end
local wikitext = format_fraction(parms, 'in', negative, leading_plus .. wholestr, numstr, denstr, parms.opt_spell_in, style)
return value, altvalue, wikitext, denominator
end  
local function extract_number(parms, text, another, no_fraction)
-- Return true, info if can extract a number from text,
-- where info is a table with the result,
-- or return false, t where t is an error message table.
-- Input can use en digits or digits in local language and can
-- have references at the end. Accepting references is intended
-- for use in infoboxes with a field for a value passed to convert.
-- Parameter another = true if the expected value is not the first.
-- Before processing, the input text is cleaned: