Difference between revisions of "Module:Math/sandbox"

From blackwiki
Jump to navigation Jump to search
blackwiki>Mr. Stradivarius
(set a seed for p.random)
blackwiki>Mr. Stradivarius
(use tabs for indentation)
Line 10: Line 10:
 
math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000))
 
math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000))
 
first = tonumber(frame.args[1]) -- if it doesn't exist it's NaN, if not a number it's nil
 
first = tonumber(frame.args[1]) -- if it doesn't exist it's NaN, if not a number it's nil
    second = tonumber(frame.args[2])
+
second = tonumber(frame.args[2])
  
    if first then -- if NaN or nil, will skip down to final return
+
if first then -- if NaN or nil, will skip down to final return
        if first <= second then -- could match if both nil, but already checked that first is a number in last line
+
if first <= second then -- could match if both nil, but already checked that first is a number in last line
            return math.random(first, second)
+
return math.random(first, second)
        end
+
end
        return math.random(first)
+
return math.random(first)
    end   
+
end   
    return math.random()
+
return math.random()
 
end
 
end
  
Line 27: Line 27:
  
 
Usage:
 
Usage:
    {{#invoke: Math | order | value }}
+
{{#invoke: Math | order | value }}
 
]]
 
]]
 
function p.order(frame)
 
function p.order(frame)
    local input_string = (frame.args[1] or frame.args.x or '0');
+
local input_string = (frame.args[1] or frame.args.x or '0');
    local input_number;
+
local input_number;
   
+
 
    input_number = p._cleanNumber( frame, input_string );
+
input_number = p._cleanNumber( frame, input_string );
    if input_number == nil then
+
if input_number == nil then
        return '<strong class="error">Formatting error: Order of magnitude input appears non-numeric</strong>'
+
return '<strong class="error">Formatting error: Order of magnitude input appears non-numeric</strong>'
    else
+
else
        return p._order( input_number )
+
return p._order( input_number )
    end     
+
end     
 
end
 
end
 
function p._order(x)
 
function p._order(x)
    if x == 0 then return 0 end
+
if x == 0 then return 0 end
    return math.floor(math.log10(math.abs(x)))
+
return math.floor(math.log10(math.abs(x)))
 
end
 
end
  
Line 51: Line 51:
  
 
Usage:
 
Usage:
    {{ #invoke: Math | precision | value }}
+
{{ #invoke: Math | precision | value }}
 
]]
 
]]
 
function p.precision( frame )
 
function p.precision( frame )
    local input_string = (frame.args[1] or frame.args.x or '0');
+
local input_string = (frame.args[1] or frame.args.x or '0');
    local trap_fraction = frame.args.check_fraction or false;
+
local trap_fraction = frame.args.check_fraction or false;
    local input_number;
+
local input_number;
   
+
 
    if type( trap_fraction ) == 'string' then
+
if type( trap_fraction ) == 'string' then
        trap_fraction = trap_fraction:lower();
+
trap_fraction = trap_fraction:lower();
        if trap_fraction == 'false' or trap_fraction == '0' or
+
if trap_fraction == 'false' or trap_fraction == '0' or
                trap_fraction == 'no' or trap_fraction == '' then
+
trap_fraction == 'no' or trap_fraction == '' then
            trap_fraction = false;
+
trap_fraction = false;
        else
+
else
            trap_fraction = true;
+
trap_fraction = true;
        end
+
end
    end
+
end
   
+
 
    if trap_fraction then
+
if trap_fraction then
        local pos = string.find( input_string, '/', 1, true );
+
local pos = string.find( input_string, '/', 1, true );
        if pos ~= nil then
+
if pos ~= nil then
            if string.find( input_string, '/', pos + 1, true ) == nil then
+
if string.find( input_string, '/', pos + 1, true ) == nil then
                local denominator = string.sub( input_string, pos+1, -1 );
+
local denominator = string.sub( input_string, pos+1, -1 );
                local denom_value = tonumber( denominator );
+
local denom_value = tonumber( denominator );
                if denom_value ~= nil then
+
if denom_value ~= nil then
                    return math.log10(denom_value);
+
return math.log10(denom_value);
                end
+
end
            end                         
+
end                         
        end
+
end
    end     
+
end     
   
+
 
    input_number, input_string = p._cleanNumber( frame, input_string );
+
input_number, input_string = p._cleanNumber( frame, input_string );
    if input_string == nil then
+
if input_string == nil then
        return '<strong class="error">Formatting error: Precision input appears non-numeric</strong>'
+
return '<strong class="error">Formatting error: Precision input appears non-numeric</strong>'
    else
+
else
        return p._precision( input_string )
+
return p._precision( input_string )
    end     
+
end     
 
end
 
end
 
function p._precision( x )     
 
function p._precision( x )     
    x = string.upper( x )
+
x = string.upper( x )
  
    local decimal = string.find( x, '.', 1, true )
+
local decimal = string.find( x, '.', 1, true )
    local exponent_pos = string.find( x, 'E', 1, true )
+
local exponent_pos = string.find( x, 'E', 1, true )
    local result = 0;
+
local result = 0;
   
+
 
    if exponent_pos ~= nil then
+
if exponent_pos ~= nil then
        local exponent = string.sub( x, exponent_pos + 1 )
+
local exponent = string.sub( x, exponent_pos + 1 )
        x = string.sub( x, 1, exponent_pos - 1 )
+
x = string.sub( x, 1, exponent_pos - 1 )
        result = result - tonumber( exponent )
+
result = result - tonumber( exponent )
    end     
+
end     
   
+
 
    if decimal ~= nil then
+
if decimal ~= nil then
        result = result + string.len( x ) - decimal
+
result = result + string.len( x ) - decimal
        return result
+
return result
    end
+
end
       
+
 
    local pos = string.len( x );
+
local pos = string.len( x );
    while x:byte(pos) == string.byte('0') do
+
while x:byte(pos) == string.byte('0') do
        pos = pos - 1
+
pos = pos - 1
        result = result - 1
+
result = result - 1
        if pos <= 0 then
+
if pos <= 0 then
            return 0
+
return 0
        end
+
end
    end
+
end
   
+
 
    return result
+
return result
 
end
 
end
  
Line 124: Line 124:
  
 
Usage:
 
Usage:
    {{#invoke:Math| max | value1 | value2 | ... }}
+
{{#invoke:Math| max | value1 | value2 | ... }}
 
OR
 
OR
    {{#invoke:Math| max }}
+
{{#invoke:Math| max }}
  
 
When used with no arguments, it takes its input from the parent
 
When used with no arguments, it takes its input from the parent
Line 132: Line 132:
 
]]
 
]]
 
function p.max( frame )
 
function p.max( frame )
    local args = frame.args;
+
local args = frame.args;
   
+
 
    if args[1] == nil then
+
if args[1] == nil then
        local parent = frame:getParent();
+
local parent = frame:getParent();
        args = parent.args;
+
args = parent.args;
    end
+
end
    local max_value = nil;
+
local max_value = nil;
   
+
 
    local i = 1;
+
local i = 1;
    while args[i] ~= nil do
+
while args[i] ~= nil do
        local val = p._cleanNumber( frame, args[i] );
+
local val = p._cleanNumber( frame, args[i] );
        if val ~= nil then
+
if val ~= nil then
            if max_value == nil or val > max_value then
+
if max_value == nil or val > max_value then
                max_value = val;
+
max_value = val;
            end
+
end
        end         
+
end         
        i = i + 1;
+
i = i + 1;
    end
+
end
 
+
 
    return max_value
+
return max_value
 
end
 
end
  
Line 160: Line 160:
  
 
Usage:
 
Usage:
    {{#invoke:Math| min | value1 | value2 | ... }}
+
{{#invoke:Math| min | value1 | value2 | ... }}
 
OR
 
OR
    {{#invoke:Math| min }}
+
{{#invoke:Math| min }}
  
 
When used with no arguments, it takes its input from the parent
 
When used with no arguments, it takes its input from the parent
Line 168: Line 168:
 
]]
 
]]
 
function p.min( frame )
 
function p.min( frame )
    local args = frame.args;
+
local args = frame.args;
   
+
 
    if args[1] == nil then
+
if args[1] == nil then
        local parent = frame:getParent();
+
local parent = frame:getParent();
        args = parent.args;
+
args = parent.args;
    end
+
end
    local min_value = nil;
+
local min_value = nil;
   
+
 
    local i = 1;
+
local i = 1;
    while args[i] ~= nil do
+
while args[i] ~= nil do
        local val = p._cleanNumber( frame, args[i] );
+
local val = p._cleanNumber( frame, args[i] );
        if val ~= nil then
+
if val ~= nil then
            if min_value == nil or val < min_value then
+
if min_value == nil or val < min_value then
                min_value = val;
+
min_value = val;
            end
+
end
        end         
+
end         
        i = i + 1;
+
i = i + 1;
    end
+
end
 
+
 
    return min_value
+
return min_value
 
end
 
end
  
 
--[[
 
--[[
 
average  
 
average  
+
 
 
Finds the average
 
Finds the average
+
 
 
Usage:
 
Usage:
    {{#invoke:Math| average | value1 | value2 | ... }}
+
{{#invoke:Math| average | value1 | value2 | ... }}
 
OR
 
OR
    {{#invoke:Math| average }}
+
{{#invoke:Math| average }}
+
 
 
When used with no arguments, it takes its input from the parent
 
When used with no arguments, it takes its input from the parent
 
frame.  Note, any values that do not evaluate to numbers are ignored.
 
frame.  Note, any values that do not evaluate to numbers are ignored.
 
]]
 
]]
 
function p.average( frame )
 
function p.average( frame )
    local args = frame.args;
+
local args = frame.args;
    if args[1] == nil then
+
if args[1] == nil then
        local parent = frame:getParent();
+
local parent = frame:getParent();
        args = parent.args;
+
args = parent.args;
    end
+
end
    local sum = 0;
+
local sum = 0;
    local count = 0;
+
local count = 0;
+
 
    local i = 1;
+
local i = 1;
    while args[i] ~= nil do
+
while args[i] ~= nil do
        local val = p._cleanNumber( frame, args[i] );
+
local val = p._cleanNumber( frame, args[i] );
        if val ~= nil then
+
if val ~= nil then
            sum = sum + val
+
sum = sum + val
            count = count + 1
+
count = count + 1
        end         
+
end         
        i = i + 1;
+
i = i + 1;
    end
+
end
+
 
    return (count == 0 and 0 or sum/count)
+
return (count == 0 and 0 or sum/count)
 
end
 
end
  
Line 231: Line 231:
  
 
Usage:
 
Usage:
    {{#invoke:Math | round | value | precision }}
+
{{#invoke:Math | round | value | precision }}
   
+
 
 
--]]
 
--]]
 
function p.round(frame)
 
function p.round(frame)
    local value, precision;
+
local value, precision;
   
+
 
    value = p._cleanNumber( frame, frame.args[1] or frame.args.value or 0 );
+
value = p._cleanNumber( frame, frame.args[1] or frame.args.value or 0 );
    precision = p._cleanNumber( frame, frame.args[2] or frame.args.precision or 0 );
+
precision = p._cleanNumber( frame, frame.args[2] or frame.args.precision or 0 );
   
+
 
    if value == nil or precision == nil then
+
if value == nil or precision == nil then
        return '<strong class="error">Formatting error: Round input appears non-numeric</strong>'
+
return '<strong class="error">Formatting error: Round input appears non-numeric</strong>'
    else
+
else
        return p._round( value, precision );
+
return p._round( value, precision );
    end     
+
end     
 
end
 
end
 
function p._round( value, precision )
 
function p._round( value, precision )
    local rescale = math.pow( 10, precision );
+
local rescale = math.pow( 10, precision );
    return math.floor( value * rescale + 0.5 ) / rescale;
+
return math.floor( value * rescale + 0.5 ) / rescale;
 
end
 
end
  
 
--[=[
 
--[=[
 
gcd
 
gcd
+
 
 
Calculates the [[greatest common divisor]] of two numbers according to the [[extended Euclidean algorithm]].
 
Calculates the [[greatest common divisor]] of two numbers according to the [[extended Euclidean algorithm]].
  
 
Usage:
 
Usage:
    {{#invoke:Math | gcd | value 1 | value 2 }}
+
{{#invoke:Math | gcd | value 1 | value 2 }}
 
]=]
 
]=]
 
function p.gcd(frame)
 
function p.gcd(frame)
    local args = frame.args
+
local args = frame.args
+
 
    if args[1] == nil then
+
if args[1] == nil then
        local parent = frame:getParent()
+
local parent = frame:getParent()
        args = parent.args
+
args = parent.args
    end
+
end
+
 
    local a = p._cleanNumber(frame, args[1])
+
local a = p._cleanNumber(frame, args[1])
    local b = p._cleanNumber(frame, args[2])
+
local b = p._cleanNumber(frame, args[2])
    if not a or not b then return end
+
if not a or not b then return end
 +
 
 +
local r = b
 +
local oldr = a
 +
while r ~= 0 do
 +
local quotient = math.floor(oldr / r)
 +
oldr, r = r, oldr - quotient * r
 +
end
 +
 
 +
if a == 0 and b < 0 or b == 0 and a < 0 then oldr = oldr * -1 end
  
    local r = b
+
return oldr
    local oldr = a
 
    while r ~= 0 do
 
        local quotient = math.floor(oldr / r)
 
        oldr, r = r, oldr - quotient * r
 
    end
 
 
    if a == 0 and b < 0 or b == 0 and a < 0 then oldr = oldr * -1 end
 
 
    return oldr
 
 
end
 
end
  
Line 290: Line 290:
  
 
Usage:
 
Usage:
    {{#invoke: Math | precision_format | number | precision }}
+
{{#invoke: Math | precision_format | number | precision }}
 
]]
 
]]
 
function p.precision_format( frame )
 
function p.precision_format( frame )
    -- For access to Mediawiki built-in formatter.
+
-- For access to Mediawiki built-in formatter.
    local lang = mw.getContentLanguage();
+
local lang = mw.getContentLanguage();
   
+
 
    local value_string, value, precision;
+
local value_string, value, precision;
    value, value_string = p._cleanNumber( frame, frame.args[1] or 0 );
+
value, value_string = p._cleanNumber( frame, frame.args[1] or 0 );
    precision = p._cleanNumber( frame, frame.args[2] or 0 );
+
precision = p._cleanNumber( frame, frame.args[2] or 0 );
   
+
 
    -- Check for non-numeric input
+
-- Check for non-numeric input
    if value == nil or precision == nil then
+
if value == nil or precision == nil then
        return '<strong class="error">Formatting error: invalid input when rounding</strong>'
+
return '<strong class="error">Formatting error: invalid input when rounding</strong>'
    end
+
end
   
+
 
    local current_precision = p._precision( value );
+
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);
  
    local order = p._order( value );
+
padding = precision;
   
+
if padding > 20 then
    -- Due to round-off effects it is neccesary to limit the returned precision under
+
padding = 20;
    -- some circumstances because the terminal digits will be inaccurately reported.
+
end
    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
+
formatted_num = formatted_num .. string.rep( '0', padding );
    if precision < current_precision then
+
end           
        value = p._round( value, precision );
+
else                  
        current_precision = p._precision( value );
+
padding = precision - current_precision
    end   
+
if padding > 20 then
   
+
padding = 20;
    local formatted_num = lang:formatNum( math.abs(value) );
+
end
    local sign;
+
formatted_num = formatted_num .. string.rep( '0', padding );
   
+
end
    -- Use proper unary minus sign rather than ASCII default
+
end
    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;
+
-- Add exponential notation, if necessary.
                if padding > 20 then
+
if order ~= 0 then
                    padding = 20;
+
-- Use proper unary minus sign rather than ASCII default
                end
+
if order < 0 then
               
+
order = '−' .. lang:formatNum( math.abs(order) );
                formatted_num = formatted_num .. string.rep( '0', padding );
+
else
            end           
+
order = lang:formatNum( order );
        else                  
+
end  
            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.
+
formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>'
    if order ~= 0 then
+
end
        -- Use proper unary minus sign rather than ASCII default
+
 
        if order < 0 then
+
return formatted_num;
            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_num;
 
 
end
 
end
  
Line 391: Line 391:
  
 
function p._cleanNumber( frame, number_string )
 
function p._cleanNumber( frame, number_string )
    if number_string == nil or number_string:len() == 0 then
+
if number_string == nil or number_string:len() == 0 then
        return nil, nil;
+
return nil, nil;
    end     
+
end     
   
+
 
    -- Attempt basic conversion
+
-- Attempt basic conversion
    local number = tonumber( number_string )
+
local number = tonumber( number_string )
   
+
 
    -- If failed, attempt to evaluate input as an expression
+
-- If failed, attempt to evaluate input as an expression
    if number == nil then         
+
if number == nil then         
        local attempt = frame:preprocess( '{{#expr: ' .. number_string .. '}}' );
+
local attempt = frame:preprocess( '{{#expr: ' .. number_string .. '}}' );
        attempt = tonumber( attempt );
+
attempt = tonumber( attempt );
        if attempt ~= nil then
+
if attempt ~= nil then
            number = attempt;
+
number = attempt;
            number_string = tostring( number );
+
number_string = tostring( number );
        else
+
else
            number = nil;
+
number = nil;
            number_string = nil;
+
number_string = nil;
        end
+
end
    else
+
else
    -- String is valid but may contain padding, clean it.
+
-- String is valid but may contain padding, clean it.
        number_string = number_string:match( "^%s*(.-)%s*$" );
+
number_string = number_string:match( "^%s*(.-)%s*$" );
    end
+
end
   
+
 
    return number, number_string;
+
return number, number_string;
 
end
 
end
  
 
return p
 
return p

Revision as of 11:04, 2 December 2013

Documentation for this module may be created at Module:Math/sandbox/doc

--[[

This module provides a number of basic mathematical operations.

]]
local p = {}

-- Generate random number
function p.random( frame )
	math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000))
	first = tonumber(frame.args[1]) -- if it doesn't exist it's NaN, if not a number it's nil
	second = tonumber(frame.args[2])

	if first then -- if NaN or nil, will skip down to final return
		if first <= second then -- could match if both nil, but already checked that first is a number in last line
			return math.random(first, second)
		end
		return math.random(first)
	end   
	return math.random()
end

--[[
order

Determine order of magnitude of a number

Usage:
{{#invoke: Math | order | value }}
]]
function p.order(frame)
	local input_string = (frame.args[1] or frame.args.x or '0');
	local input_number;

	input_number = p._cleanNumber( frame, input_string );
	if input_number == nil then
		return '<strong class="error">Formatting error: Order of magnitude input appears non-numeric</strong>'
	else
		return p._order( input_number )
	end    
end
function p._order(x)
	if x == 0 then return 0 end
	return math.floor(math.log10(math.abs(x)))
end

--[[
precision

Detemines the precision of a number using the string representation

Usage:
{{ #invoke: Math | precision | value }}
]]
function p.precision( frame )
	local input_string = (frame.args[1] or frame.args.x or '0');
	local trap_fraction = frame.args.check_fraction or false;
	local input_number;

	if type( trap_fraction ) == 'string' then
		trap_fraction = trap_fraction: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.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 );
				if denom_value ~= nil then
					return math.log10(denom_value);
				end
			end                        
		end
	end    

	input_number, input_string = p._cleanNumber( frame, input_string );
	if input_string == nil then
		return '<strong class="error">Formatting error: Precision input appears non-numeric</strong>'
	else
		return p._precision( input_string )
	end    
end
function p._precision( x )    
	x = string.upper( x )

	local decimal = string.find( x, '.', 1, true )
	local exponent_pos = string.find( x, 'E', 1, true )
	local result = 0;

	if exponent_pos ~= nil then
		local exponent = string.sub( x, exponent_pos + 1 )
		x = string.sub( x, 1, exponent_pos - 1 )
		result = result - tonumber( exponent )
	end    

	if decimal ~= nil then
		result = result + string.len( x ) - decimal
		return result
	end

	local pos = string.len( x );
	while x:byte(pos) == string.byte('0') do
		pos = pos - 1
		result = result - 1
		if pos <= 0 then
			return 0
		end
	end

	return result
end

--[[
max

Finds the maximum argument

Usage:
{{#invoke:Math| max | value1 | value2 | ... }}
OR
{{#invoke:Math| max }}

When used with no arguments, it takes its input from the parent
frame.  Note, any values that do not evaluate to numbers are ignored.
]]
function p.max( frame )
	local args = frame.args;

	if args[1] == nil then
		local parent = frame:getParent();
		args = parent.args;
	end
	local max_value = nil;

	local i = 1;
	while args[i] ~= nil do
		local val = p._cleanNumber( 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_value
end

--[[
min 

Finds the minimum argument

Usage:
{{#invoke:Math| min | value1 | value2 | ... }}
OR
{{#invoke:Math| min }}

When used with no arguments, it takes its input from the parent
frame.  Note, any values that do not evaluate to numbers are ignored.
]]
function p.min( frame )
	local args = frame.args;

	if args[1] == nil then
		local parent = frame:getParent();
		args = parent.args;
	end
	local min_value = nil;

	local i = 1;
	while args[i] ~= nil do
		local val = p._cleanNumber( frame, args[i] );
		if val ~= nil then
			if min_value == nil or val < min_value then
				min_value = val;
			end
		end        
		i = i + 1;
	end

	return min_value
end

--[[
average 

Finds the average

Usage:
{{#invoke:Math| average | value1 | value2 | ... }}
OR
{{#invoke:Math| average }}

When used with no arguments, it takes its input from the parent
frame.  Note, any values that do not evaluate to numbers are ignored.
]]
function p.average( frame )
	local args = frame.args;
	if args[1] == nil then
		local parent = frame:getParent();
		args = parent.args;
	end
	local sum = 0;
	local count = 0;

	local i = 1;
	while args[i] ~= nil do
		local val = p._cleanNumber( frame, args[i] );
		if val ~= nil then
			sum = sum + val
			count = count + 1
		end        
		i = i + 1;
	end

	return (count == 0 and 0 or sum/count)
end

--[[
round

Rounds a number to specified precision

Usage:
{{#invoke:Math | round | value | precision }}

--]]
function p.round(frame)
	local value, precision;

	value = p._cleanNumber( frame, frame.args[1] or frame.args.value or 0 );
	precision = p._cleanNumber( frame, frame.args[2] or frame.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 p._round( value, precision );
	end    
end
function p._round( value, precision )
	local rescale = math.pow( 10, precision );
	return math.floor( value * rescale + 0.5 ) / rescale;
end

--[=[
gcd

Calculates the [[greatest common divisor]] of two numbers according to the [[extended Euclidean algorithm]].

Usage:
{{#invoke:Math | gcd | value 1 | value 2 }}
]=]
function p.gcd(frame)
	local args = frame.args

	if args[1] == nil then
		local parent = frame:getParent()
		args = parent.args
	end

	local a = p._cleanNumber(frame, args[1])
	local b = p._cleanNumber(frame, args[2])
	if not a or not b then return end

	local r = b
	local oldr = a
	while r ~= 0 do
		local quotient = math.floor(oldr / r)
		oldr, r = r, oldr - quotient * r
	end

	if a == 0 and b < 0 or b == 0 and a < 0 then oldr = oldr * -1 end

	return oldr
end

--[[
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 p.precision_format( frame )
	-- For access to Mediawiki built-in formatter.
	local lang = mw.getContentLanguage();

	local value_string, value, precision;
	value, value_string = p._cleanNumber( frame, frame.args[1] or 0 );
	precision = p._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 = 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_num;
end

--[[
Helper function that interprets the input numerically.  If the 
input does not appear to be a number, attempts evaluating it as
a parser functions expression.
]]

function p._cleanNumber( frame, number_string )
	if number_string == nil or number_string:len() == 0 then
		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 attempt = frame:preprocess( '{{#expr: ' .. number_string .. '}}' );
		attempt = tonumber( attempt );
		if attempt ~= nil then
			number = attempt;
			number_string = tostring( number );
		else
			number = nil;
			number_string = nil;
		end
	else
		-- String is valid but may contain padding, clean it.
		number_string = number_string:match( "^%s*(.-)%s*$" );
	end

	return number, number_string;
end

return p