Changes

Jump to navigation Jump to search

Module:Math

4,860 bytes added, 01:26, 10 September 2019
m
41 revisions imported
local z = {}require( "mw.language" );--[[
This module provides a number of basic mathematical operations. ]] local yesno, getArgs -- lazily initialized local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.local wrap = {} -- Clean numeric valueHolds wrapper functions that process arguments from #invoke. These act as intemediary between functions meant for #invoke and functions meant for Lua. --[[Helper functions used to avoid redundant code.]] local function zerr(msg) -- Generates wikitext error messages. return mw.ustring._cleanNumberformat('<strong class="error">Formatting error: %s</strong>', msg)end local function unpackNumberArgs( frameargs) -- Returns an unpacked list of arguments specified with numerical keys. local ret = {} for k, number_string v in pairs(args)do if number_string == nil or number_string:lentype(k) == 0 'number' then table.insert(ret, v) end end return nil, nil;unpack(ret) end local function makeArgArray(...) -- Attempt basic conversionMakes an array of arguments from a list of arguments that might include nils. local args = {...} -- Table of arguments. It might contain nils or non-number values, so we can't use ipairs. local nums = {} -- Stores the numbers of valid numerical arguments. local ret = tonumber{} for k, v in pairs(args) do v = p._cleanNumber(v) if v then nums[#nums + 1] = k args[k] = v end end table.sort(nums) for i, num in ipairs( number_string nums)do ret[#ret + 1] = args[num] end return retend local function fold(func, ...) -- If failedUse a function on all supplied arguments, attempt to evaluate input and return the result. The function must accept two numbers as parameters, -- and must return a number as an expressionoutput. This number is then supplied as input to the next function call. local vals = makeArgArray(...) local count = #vals -- The number of valid arguments if number count == 0 then return -- Exit if we have no valid args, otherwise removing the first arg would cause an error. nil then , 0 end local ret = table.remove(vals, 1) for _, val in ipairs(vals) do ret = func(ret, val) end return ret, countend --[[Fold arguments by selectively choosing values (func should return when to choose the current "dominant" value).]]local function binary_fold(func, ...) local attempt value = framefold((function(a, b) if func(a, b) then return a else return b end end), ...) return valueend --[[random Generate a random number Usage:preprocess( '{{#invoke: Math | random }}{{#invoke: Math | random | maximum value }}{{#exprinvoke: Math | random | minimum value | maximum value }}]] function wrap.random(args) local first = p._cleanNumber(args[1]) local second = p._cleanNumber(args[2]) return p._random(first, second)end function p._random(first, second) math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000)) -- math.random will throw an error if given an explicit nil parameter, so we need to use if statements to check the params. if first and second then if first <= second then -- math.random doesn' t allow the first number to be greater than the second. return math. number_string random(first, second) end elseif first then return math.random(first) else return math. 'random() endend --[[order Determine order of magnitude of a number Usage:{{#invoke: Math | order | value }}]] function wrap.order(args) local input_string = (args[1] or args.x or '0' ); attempt local input_number = tonumberp._cleanNumber( attempt input_string); if attempt ~input_number == nil then number return err('order of magnitude input appears non-numeric') else return p._order(input_number) end end function p._order(x) if x == attempt;0 then return 0 end number_string = tostring return math.floor(math.log10(math.abs( number x))); elseend --[[precision  Detemines the precision of a number using the string representation Usage:{{ #invoke: Math | precision | value }}]] function wrap.precision(args) local input_string = nil(args[1] or args.x or '0'); number_string local trap_fraction = nilargs.check_fraction; local input_number; if not yesno then yesno = require('Module:Yesno') end else if yesno(trap_fraction, true) then -- String is valid but may contain paddingReturns true for all input except nil, false, "no", "n", clean it"0" and a few others. See [[Module:Yesno]]. number_string local pos = number_string:matchstring.find( "^%s*input_string, '/', 1, true); if pos ~= nil then if string.find(input_string, '/', pos + 1, true) == nil then local denominator = string.sub(input_string, pos+1, -1)%s*$" ; local denom_value = tonumber(denominator); if denom_value ~= nil then return math.log10(denom_value); end end end end return number input_number, number_stringinput_string = p._cleanNumber(input_string); if input_string == nil then return err('precision input appears non-numeric') else return p._precision(input_string) end
end
-- Generate random function p._precision(x) if type(x) == 'number' thenfunction z x = tostring(x) end x = string.randomupper( frame x) first local decimal = tonumberx:find(frame'%.args[1]') -- if it doesn local exponent_pos = x:find('t exist itE's NaN, ) local result = 0;  if not a number it's exponent_pos ~= nilthen local exponent = string.sub(x, exponent_pos + 1) second x = string.sub(x, 1, exponent_pos - 1) result = result - tonumber(frame.args[2]exponent) end
if first decimal ~= nil then -- if NaN or nil, will skip down to final return if first < result = second then result + string.len(x) -- could match if both nil, but already checked that first is a number in last linedecimal return mathresult end  local pos = string.randomlen(first, secondx); end return math while x:byte(pos) == string.randombyte(first'0')do pos = pos - 1 result = result - 1 if pos <= 0 then return 0 end end  return math.random()result
end
 -- Determine order of magnitude[[max Finds the maximum argumentfunction zUsage:{{#invoke:Math| max | value1 | value2 | .order(frame) local input_string = (frame.args[1] or frame.args}} Note, any values that do not evaluate to numbers are ignored.x or '0'); local input_number;]] input_number = zfunction wrap._cleanNumbermax( frame, input_string args); if input_number == nil then return '<strong class="error">Formatting error: Order of magnitude input appears non-numeric</strong>' else return zp._order_max( input_number unpackNumberArgs(args)) end
end
 function zp._order_max(x...) if x local max_value == 0 then binary_fold((function(a, b) return 0 a > b end return math), .floor(math.log10(math.abs(x))) if max_value then return max_value end
end
-- Determines precision of a number using the string representationfunction z.precision( frame ) local input_string = (frame.args[1] or frame.args.x or '0');[ local trap_fraction = frame.args.check_fraction or false;median local input_number; Find the median of set of numbers if type( trap_fraction ) == 'string' then trap_fraction = trap_fractionUsage:lower(); if trap_fraction == 'false' or trap_fraction == '0' or trap_fraction == 'no' or trap_fraction == '' then trap_fraction = false; else trap_fraction = true; end end if trap_fraction then local pos = string{{#invoke:Math | median | number1 | number2 | .find( input_string, '/', 1, true ); if pos ~= nil then if string.find( input_string, '/', pos + 1, true ) == nil then local denominator = string.sub( input_string, pos+1, -1 );}} local denom_value = tonumber( denominator );OR if denom_value ~= nil then{{#invoke:Math | median }} return math.log10(denom_value);]] end end end end input_number, input_string = zfunction wrap._cleanNumbermedian( frame, input_string args); if input_string == nil then return '<strong class="error">Formatting error: Precision input appears non-numeric</strong>' else return zp._precision_median(unpackNumberArgs( input_string args)) end
end
function z._precision( x )
x = string.upper( x )
local decimal = stringfunction p.find_median( x, '.', 1, true ..) local exponent_pos vals = stringmakeArgArray(...find( x, 'E', 1, true ) local result = 0; if exponent_pos ~count = nil then#vals local exponent = string table.subsort( x, exponent_pos + 1 vals) x = string.sub( x, 1, exponent_pos - 1 ) result if count = result - tonumber( exponent ) end if decimal ~= nil 0 then result = result + string.len( x ) - decimal return result0 end local pos = string if p.len_mod( x ); while x:byte(poscount, 2) == string.byte('0') dothen pos = pos - return (vals[count/2] + vals[count/2+1])/2 result = result - 1 else if pos <= 0 then return 0 endvals[math.ceil(count/2)] end return result
end
-- Finds maximum argument[[function z.max( frame )min local args = frame.args; Finds the minimum argument if args[1] == nil then local parent = frameUsage:getParent(); args = parent{{#invoke:Math| min | value1 | value2 | ...args;}} endOR local max_value = nil;{{#invoke:Math| min }} local i = 1;When used with no arguments, it takes its input from the parent while args[iframe. Note, any values that do not evaluate to numbers are ignored.]] ~= nil do local val = zfunction wrap._cleanNumbermin( frame, args[i] ); if val ~= nil then if max_value == nil or val > max_value then max_value = val; end end i = i + 1; end return max_valuep._min(unpackNumberArgs(args))
end
-- Finds minimum argumentfunction zp.min_min( frame ...) local args min_value = frame.args; if args[1] == nil then local parent = frame:getParentbinary_fold((function(a, b) return a < b end); args = parent, ..args; end local min_value = nil; local i = 1; while args[i] ~= nil do local val = z._cleanNumber( frame, args[i] ); if val ~= nil then if min_value == nil or val < min_value then return min_value = val; end end i = i + 1; end return min_value
end
-- Rounds a number to specified precision[[sum  function z.round(frame)Finds the sum local value, precision; Usage: value = z{{#invoke:Math| sum | value1 | value2 | ._cleanNumber( frame, frame.args[1] or frame.args.value or 0 );}}OR{{#invoke:Math| sum }}  precision = z._cleanNumber( frameNote, frameany values that do not evaluate to numbers are ignored.args[2]] or frame function wrap.sum(args.precision or 0 ); if value == nil or precision == nil then return '<strong class="error">Formatting error: Round input appears non-numeric</strong>' else return zp._round_sum( value, precision unpackNumberArgs(args)); end
end
 function zp._round_sum( value, precision ...) local rescale sums, count = math.powfold((function( 10a, precision b); return matha + b end), ..floor( value * rescale + 0.5 ) / rescale; if not sums then return 0 else return sums end
end
-- Rounds a number to the specified precision and formats according to rules -- originally used for {{template:Rnd}}. Output is a string.function z.precision_format( frame ) -- For access to Mediawiki built-in formatter. local lang = mw.getContentLanguage(); local value_string, value, precision; value, value_string = z._cleanNumber( frame, frame.args[1] or 0 ); precision = z._cleanNumber( frame, frame.args[2] or 0 ); -- Check for non-numeric input if value == nil or precision == nil then return '<strong class="error">Formatting error: invalid input when rounding</strong>' end local current_precision = z._precision( value );average
local order = z._order( value ); -- Due to round-off effects it is neccesary to limit Finds the returned precision under -- some circumstances because the terminal digits will be inaccurately reported. if order + precision >= 14 then orig_precision = z._precision( value_string ); if order + orig_precision >= 14 then precision = 13 - order; end endaverage
-- If rounding off, truncate extra digits if precision < current_precision then value = z._round( value, precision ); current_precision = z._precision( value ); end local formatted_num = langUsage:formatNum( math.abs(value) ); local sign; -- Use proper unary minus sign rather than ASCII default if value < 0 then sign = '−'; else sign = ''; end -- Handle cases requiring scientific notation if string.find( formatted_num, 'E', 1, true ) ~= nil or math.abs(order) >= 9 then value = value * math.pow( 10, -order ); current_precision = current_precision + order; precision = precision + order; formatted_num = lang{{#invoke:formatNum( mathMath| average | value1 | value2 | .abs(value) ); else order = 0; end formatted_num = sign .. formatted_num; -- Pad with zeros, if needed if current_precision < precision then local padding; if current_precision <= 0 then}} if precision > 0 thenOR local zero_sep = lang:formatNum( 1.1 ); formatted_num = formatted_num .. zero_sep{{#invoke:sub(2,2);Math| average }}
padding = precision; if padding > 20 then padding = 20; end formatted_num = formatted_num .. string.rep( '0'Note, padding ); end else padding = precision - current_precision if padding > 20 then padding = 20; end formatted_num = formatted_num any values that do not evaluate to numbers are ignored.. string.rep( '0', padding ); end end]]
-- Add exponential notation, if necessaryfunction wrap.average(args) if order ~= 0 then -- Use proper unary minus sign rather than ASCII default if order < 0 then order = '−' . return p. lang:formatNum_average( math.absunpackNumberArgs(orderargs) ); else order = lang:formatNum( order ); end formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>' end return formatted_num;
end
function p._average(...) local sum, count = fold((function(a, b) return za + b end), ...) if not sum then return 0 else return sum / count endend --[[round Rounds a number to specified precision Usage:{{#invoke:Math | round | value | precision }} --]] function wrap.round(args) local value = p._cleanNumber(args[1] or args.value or 0) local precision = p._cleanNumber(args[2] or args.precision or 0) if value == nil or precision == nil then return err('round input appears non-numeric') else return p._round(value, precision) end end function p._round(value, precision) local rescale = math.pow(10, precision or 0); return math.floor(value * rescale + 0.5) / rescale;end --[[log10 returns the log (base 10) of a number Usage:{{#invoke:Math | log10 | x }}]] function wrap.log10(args) return math.log10(args[1])end --[[mod Implements the modulo operator Usage:{{#invoke:Math | mod | x | y }} --]] function wrap.mod(args) local x = p._cleanNumber(args[1]) local y = p._cleanNumber(args[2]) if not x then return err('first argument to mod appears non-numeric') elseif not y then return err('second argument to mod appears non-numeric') else return p._mod(x, y) end end function p._mod(x, y) local ret = x % y if not (0 <= ret and ret < y) then ret = 0 end return retend --[[gcd Calculates the greatest common divisor of multiple numbers Usage:{{#invoke:Math | gcd | value 1 | value 2 | value 3 | ... }}--]] function wrap.gcd(args) return p._gcd(unpackNumberArgs(args))end function p._gcd(...) local function findGcd(a, b) local r = b local oldr = a while r ~= 0 do local quotient = math.floor(oldr / r) oldr, r = r, oldr - quotient * r end if oldr < 0 then oldr = oldr * -1 end return oldr end local result, count = fold(findGcd, ...) return resultend --[[precision_format Rounds a number to the specified precision and formats according to rules originally used for {{template:Rnd}}. Output is a string. Usage:{{#invoke: Math | precision_format | number | precision }}]] function wrap.precision_format(args) local value_string = args[1] or 0 local precision = args[2] or 0 return p._precision_format(value_string, precision)end function p._precision_format(value_string, precision) -- For access to Mediawiki built-in formatter. local lang = mw.getContentLanguage();  local value value, value_string = p._cleanNumber(value_string) precision = p._cleanNumber(precision)  -- Check for non-numeric input if value == nil or precision == nil then return err('invalid input when rounding') end  local current_precision = p._precision(value) local order = p._order(value)  -- Due to round-off effects it is neccesary to limit the returned precision under -- some circumstances because the terminal digits will be inaccurately reported. if order + precision >= 14 then orig_precision = p._precision(value_string) if order + orig_precision >= 14 then precision = 13 - order; end end  -- If rounding off, truncate extra digits if precision < current_precision then value = p._round(value, precision) current_precision = p._precision(value) end   local formatted_num = lang:formatNum(math.abs(value)) local sign  -- Use proper unary minus sign rather than ASCII default if value < 0 then sign = '−' else sign = '' end   -- Handle cases requiring scientific notation if string.find(formatted_num, 'E', 1, true) ~= nil or math.abs(order) >= 9 then value = value * math.pow(10, -order) current_precision = current_precision + order precision = precision + order formatted_num = lang:formatNum(math.abs(value)) else order = 0; end formatted_num = sign .. formatted_num  -- Pad with zeros, if needed if current_precision < precision then local padding if current_precision <= 0 then if precision > 0 then local zero_sep = lang:formatNum(1.1) formatted_num = formatted_num .. zero_sep:sub(2,2)  padding = precision if padding > 20 then padding = 20 end  formatted_num = formatted_num .. string.rep('0', padding) end else padding = precision - current_precision if padding > 20 then padding = 20 end formatted_num = formatted_num .. string.rep('0', padding) end end  -- Add exponential notation, if necessary. if order ~= 0 then -- Use proper unary minus sign rather than ASCII default if order < 0 then order = '−' .. lang:formatNum(math.abs(order)) else order = lang:formatNum(order) end   formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>' end  return formatted_numend --[[Helper function that interprets the input numerically. If the input does not appear to be a number, attempts evaluating it asa 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[xX]') 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_stringend --[[Wrapper function that does basic argument processing. This ensures that all functions from #invoke can use either the currentframe or the parent frame, and it also trims whitespace for all arguments and removes blank arguments.]] local mt = { __index = function(t, k) return function(frame) if not getArgs then getArgs = require('Module:Arguments').getArgs end return wrap[k](getArgs(frame)) -- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed. endend } return setmetatable(p, mt)

Navigation menu