Jump to content

Module:Math

Permanently protected module
From Wikipedia, the free encyclopedia

--[[This module provides a number of basic mathematical operations.]]localyesno,getArgs-- lazily initializedlocalp={}-- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.localwrap={}-- Holds 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.]]localfunctionerr(msg)-- Generates wikitext error messages.returnmw.ustring.format('<strong class="error">Formatting error: %s</strong>',msg)endlocalfunctionunpackNumberArgs(args)-- Returns an unpacked list of arguments specified with numerical keys.localret={}fork,vinpairs(args)doiftype(k)=='number'thentable.insert(ret,v)endendreturnunpack(ret)endlocalfunctionmakeArgArray(...)-- Makes an array of arguments from a list of arguments that might include nils.localargs={...}-- Table of arguments. It might contain nils or non-number values, so we can't use ipairs.localnums={}-- Stores the numbers of valid numerical arguments.localret={}fork,vinpairs(args)dov=p._cleanNumber(v)ifvthennums[#nums+1]=kargs[k]=vendendtable.sort(nums)fori,numinipairs(nums)doret[#ret+1]=args[num]endreturnretendlocalfunctionfold(func,...)-- Use a function on all supplied arguments, and return the result. The function must accept two numbers as parameters,-- and must return a number as an output. This number is then supplied as input to the next function call.localvals=makeArgArray(...)localcount=#vals-- The number of valid argumentsifcount==0thenreturn-- Exit if we have no valid args, otherwise removing the first arg would cause an error.nil,0endlocalret=table.remove(vals,1)for_,valinipairs(vals)doret=func(ret,val)endreturnret,countend--[[Fold arguments by selectively choosing values (func should return when to choose the current "dominant" value).]]localfunctionbinary_fold(func,...)localvalue=fold((function(a,b)iffunc(a,b)thenreturnaelsereturnbendend),...)returnvalueend--[[randomGenerate a random numberUsage:{{#invoke: Math | random }}{{#invoke: Math | random | maximum value }}{{#invoke: Math | random | minimum value | maximum value }}]]functionwrap.random(args)localfirst=p._cleanNumber(args[1])localsecond=p._cleanNumber(args[2])returnp._random(first,second)endfunctionp._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.iffirstandsecondtheniffirst<=secondthen-- math.random doesn't allow the first number to be greater than the second.returnmath.random(first,second)endelseiffirstthenreturnmath.random(first)elsereturnmath.random()endend--[[orderDetermine order of magnitude of a numberUsage:{{#invoke: Math | order | value }}]]functionwrap.order(args)localinput_string=(args[1]orargs.xor'0');localinput_number=p._cleanNumber(input_string);ifinput_number==nilthenreturnerr('order of magnitude input appears non-numeric')elsereturnp._order(input_number)endendfunctionp._order(x)ifx==0thenreturn0endreturnmath.floor(math.log10(math.abs(x)))end--[[precisionDetemines the precision of a number using the string representationUsage:{{ #invoke: Math | precision | value }}]]functionwrap.precision(args)localinput_string=(args[1]orargs.xor'0');localtrap_fraction=args.check_fraction;localinput_number;ifnotyesnothenyesno=require('Module:Yesno')endifyesno(trap_fraction,true)then-- Returns true for all input except nil, false, "no", "n", "0" and a few others. See [[Module:Yesno]].localpos=string.find(input_string,'/',1,true);ifpos~=nilthenifstring.find(input_string,'/',pos+1,true)==nilthenlocaldenominator=string.sub(input_string,pos+1,-1);localdenom_value=tonumber(denominator);ifdenom_value~=nilthenreturnmath.log10(denom_value);endendendendinput_number,input_string=p._cleanNumber(input_string);ifinput_string==nilthenreturnerr('precision input appears non-numeric')elsereturnp._precision(input_string)endendfunctionp._precision(x)iftype(x)=='number'thenx=tostring(x)endx=string.upper(x)localdecimal=x:find('%.')localexponent_pos=x:find('E')localresult=0;ifexponent_pos~=nilthenlocalexponent=string.sub(x,exponent_pos+1)x=string.sub(x,1,exponent_pos-1)result=result-tonumber(exponent)endifdecimal~=nilthenresult=result+string.len(x)-decimalreturnresultendlocalpos=string.len(x);whilex:byte(pos)==string.byte('0')dopos=pos-1result=result-1ifpos<=0thenreturn0endendreturnresultend--[[maxFinds the maximum argumentUsage:{{#invoke:Math| max | value1 | value2 | ... }}Note, any values that do not evaluate to numbers are ignored.]]functionwrap.max(args)returnp._max(unpackNumberArgs(args))endfunctionp._max(...)localmax_value=binary_fold((function(a,b)returna>bend),...)ifmax_valuethenreturnmax_valueendend--[[medianFind the median of set of numbersUsage:{{#invoke:Math | median | number1 | number2 | ...}}OR{{#invoke:Math | median }}]]functionwrap.median(args)returnp._median(unpackNumberArgs(args))endfunctionp._median(...)localvals=makeArgArray(...)localcount=#valstable.sort(vals)ifcount==0thenreturn0endifp._mod(count,2)==0thenreturn(vals[count/2]+vals[count/2+1])/2elsereturnvals[math.ceil(count/2)]endend--[[minFinds the minimum argumentUsage:{{#invoke:Math| min | value1 | value2 | ... }}OR{{#invoke:Math| min }}When used with no arguments, it takes its input from the parentframe. Note, any values that do not evaluate to numbers are ignored.]]functionwrap.min(args)returnp._min(unpackNumberArgs(args))endfunctionp._min(...)localmin_value=binary_fold((function(a,b)returna<bend),...)ifmin_valuethenreturnmin_valueendend--[[sumFinds the sumUsage:{{#invoke:Math| sum | value1 | value2 | ... }}OR{{#invoke:Math| sum }}Note, any values that do not evaluate to numbers are ignored.]]functionwrap.sum(args)returnp._sum(unpackNumberArgs(args))endfunctionp._sum(...)localsums,count=fold((function(a,b)returna+bend),...)ifnotsumsthenreturn0elsereturnsumsendend--[[averageFinds the averageUsage:{{#invoke:Math| average | value1 | value2 | ... }}OR{{#invoke:Math| average }}Note, any values that do not evaluate to numbers are ignored.]]functionwrap.average(args)returnp._average(unpackNumberArgs(args))endfunctionp._average(...)localsum,count=fold((function(a,b)returna+bend),...)ifnotsumthenreturn0elsereturnsum/countendend--[[roundRounds a number to specified precisionUsage:{{#invoke:Math | round | value | precision }}--]]functionwrap.round(args)localvalue=p._cleanNumber(args[1]orargs.valueor0)localprecision=p._cleanNumber(args[2]orargs.precisionor0)ifvalue==nilorprecision==nilthenreturnerr('round input appears non-numeric')elsereturnp._round(value,precision)endendfunctionp._round(value,precision)localrescale=math.pow(10,precisionor0);returnmath.floor(value*rescale+0.5)/rescale;end--[[log10returns the log (base 10) of a numberUsage:{{#invoke:Math | log10 | x }}]]functionwrap.log10(args)returnmath.log10(args[1])end--[[modImplements the modulo operatorUsage:{{#invoke:Math | mod | x | y }}--]]functionwrap.mod(args)localx=p._cleanNumber(args[1])localy=p._cleanNumber(args[2])ifnotxthenreturnerr('first argument to mod appears non-numeric')elseifnotythenreturnerr('second argument to mod appears non-numeric')elsereturnp._mod(x,y)endendfunctionp._mod(x,y)localret=x%yifnot(0<=retandret<y)thenret=0endreturnretend--[[gcdCalculates the greatest common divisor of multiple numbersUsage:{{#invoke:Math | gcd | value 1 | value 2 | value 3 | ... }}--]]functionwrap.gcd(args)returnp._gcd(unpackNumberArgs(args))endfunctionp._gcd(...)localfunctionfindGcd(a,b)localr=blocaloldr=awhiler~=0dolocalquotient=math.floor(oldr/r)oldr,r=r,oldr-quotient*rendifoldr<0thenoldr=oldr*-1endreturnoldrendlocalresult,count=fold(findGcd,...)returnresultend--[[precision_formatRounds a number to the specified precision and formats according to rulesoriginally used for {{template:Rnd}}. Output is a string.Usage:{{#invoke: Math | precision_format | number | precision }}]]functionwrap.precision_format(args)localvalue_string=args[1]or0localprecision=args[2]or0returnp._precision_format(value_string,precision)endfunctionp._precision_format(value_string,precision)-- For access to Mediawiki built-in formatter.locallang=mw.getContentLanguage();localvaluevalue,value_string=p._cleanNumber(value_string)precision=p._cleanNumber(precision)-- Check for non-numeric inputifvalue==nilorprecision==nilthenreturnerr('invalid input when rounding')endlocalcurrent_precision=p._precision(value)localorder=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.iforder+precision>=14theniforder+p._precision(value_string)>=14thenprecision=13-order;endend-- If rounding off, truncate extra digitsifprecision<current_precisionthenvalue=p._round(value,precision)current_precision=p._precision(value)endlocalformatted_num=lang:formatNum(math.abs(value))localsign-- Use proper unary minus sign rather than ASCII defaultifvalue<0thensign='−'elsesign=''end-- Handle cases requiring scientific notationifstring.find(formatted_num,'E',1,true)~=nilormath.abs(order)>=9thenvalue=value*math.pow(10,-order)current_precision=current_precision+orderprecision=precision+orderformatted_num=lang:formatNum(math.abs(value))elseorder=0;endformatted_num=sign..formatted_num-- Pad with zeros, if neededifcurrent_precision<precisionthenlocalpaddingifcurrent_precision<=0thenifprecision>0thenlocalzero_sep=lang:formatNum(1.1)formatted_num=formatted_num..zero_sep:sub(2,2)padding=precisionifpadding>20thenpadding=20endformatted_num=formatted_num..string.rep('0',padding)endelsepadding=precision-current_precisionifpadding>20thenpadding=20endformatted_num=formatted_num..string.rep('0',padding)endend-- Add exponential notation, if necessary.iforder~=0then-- Use proper unary minus sign rather than ASCII defaultiforder<0thenorder='−'..lang:formatNum(math.abs(order))elseorder=lang:formatNum(order)endformatted_num=formatted_num..'<span style="margin:0 .15em 0 .25em">×</span>10<sup>'..order..'</sup>'endreturnformatted_numend--[[divideImplements the division operatorUsage:{{#invoke:Math | divide | x | y | round= | precision= }}--]]functionwrap.divide(args)localx=args[1]localy=args[2]localround=args.roundlocalprecision=args.precisionifnotyesnothenyesno=require('Module:Yesno')endreturnp._divide(x,y,yesno(round),precision)endfunctionp._divide(x,y,round,precision)ify==nilory==""thenreturnerr("Empty divisor")elseifnottonumber(y)theniftype(y)=='string'andstring.sub(y,1,1)=='<'thenreturnyelsereturnerr("Not a number: "..y)endelseifx==nilorx==""thenreturnerr("Empty dividend")elseifnottonumber(x)theniftype(x)=='string'andstring.sub(x,1,1)=='<'thenreturnxelsereturnerr("Not a number: "..x)endelselocalz=x/yifroundthenreturnp._round(z,0)elseifprecisionthenreturnp._round(z,precision)elsereturnzendendend--[[Helper function that interprets the input numerically. If theinput does not appear to be a number, attempts evaluating it asa parser functions expression.]]functionp._cleanNumber(number_string)iftype(number_string)=='number'then-- We were passed a number, so we don't need to do any processing.returnnumber_string,tostring(number_string)elseiftype(number_string)~='string'ornotnumber_string:find('%S')then-- We were passed a non-string or a blank string, so exit.returnnil,nil;end-- Attempt basic conversionlocalnumber=tonumber(number_string)-- If failed, attempt to evaluate input as an expressionifnumber==nilthenlocalsuccess,result=pcall(mw.ext.ParserFunctions.expr,number_string)ifsuccessthennumber=tonumber(result)number_string=tostring(number)elsenumber=nilnumber_string=nilendelsenumber_string=number_string:match("^%s*(.-)%s*$")-- String is valid but may contain padding, clean it.number_string=number_string:match("^%+(.*)$")ornumber_string-- Trim any leading + signs.ifnumber_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)endendreturnnumber,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.]]localmt={__index=function(t,k)returnfunction(frame)ifnotgetArgsthengetArgs=require('Module:Arguments').getArgsendreturnwrap[k](getArgs(frame))-- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed.endend}returnsetmetatable(p,mt)
close