Module:Rational

Revision as of 09:32, 3 October 2022 by Plumtree (talk | contribs) (pow() and modulo_mul() implemented)
Module documentation[view] [edit] [history] [purge]
This module primarily serves as a library for other modules and has no corresponding template.


Introspection summary for Module:Rational 
Functions provided (25)
Line Function Params
5 new (n, m)
36 copy (a)
45 mul (a, b)
84 inv (a)
112 div (a, b)
117 pow (a, b)
157 modulo_mul (a, b)
193 add (a, b)
251 sub (a, b)
256 abs (a)
266 lt (a, b)
276 leq (a, b)
286 gt (a, b)
296 geq (a, b)
306 eq (a, b)
312 is_int (a)
333 max_prime (a)
352 is_prime_ratio (a)
374 as_pair (a)
407 as_ratio (a, separator)
414 as_table (a)
419 as_float (a)
426 as_ket (a, frame)
477 parse (unparsed)
507 ket (invokable) (frame)
Lua modules required (1)
Variable Module Functions used
u Module:Utils signum
prime_factorization_raw
is_prime

No function descriptions were provided. The Lua code may have further information.


local u = require('Module:Utils')
local p = {}

-- construct a rational number n/m
function p.new(n, m)
	m = m or 1
	if n == 0 then
		if m == 0 then
			return { nan = true }
		else
			return { zero = true, sign = u.signum(m) }
		end
	else
		if m == 0 then
			return { inf = true, sign = u.signum(n) }
		end
	end
	local sign = u.signum(n) * u.signum(m)
	n = n * u.signum(n)
	m = m * u.signum(m)
	local n_factors = u.prime_factorization_raw(n)
	local m_factors = u.prime_factorization_raw(m)
	local factors = n_factors
	factors.sign = sign
	for factor, power in pairs(m_factors) do
		factors[factor] = factors[factor] or 0
		factors[factor] = factors[factor] - power
		if factors[factor] == 0 then
			factors[factor] = nil
		end
	end
	return factors
end

-- copy a rational number
function p.copy(a)
	b = {}
	for factor, power in pairs(a) do
		b[factor] = power
	end
	return b
end

-- multiply two rational numbers; integers are also allowed
function p.mul(a, b)
	if type(a) == 'number' then
		a = p.new(a)
	end
	if type(b) == 'number' then
		b = p.new(b)
	end
	-- special case: NaN
	if a.nan or b.nan then
		return { nan = true }
	end
	-- special case: infinities
	if (a.inf and not b.zero) or (b.inf and not a.zero) then
		return { inf = true, sign = a.sign * b.sign }
	end
	-- special case: infinity * zero
	if (a.inf and b.zero) or (b.inf and a.zero) then
		return { nan = true }
	end
	-- special case: zeros
	if a.zero or b.zero then
		return { zero = true, sign = a.sign * b.sign }
	end
	-- regular case: both not NaN, not infinities, not zeros
	local c = p.copy(a)
	for factor, power in pairs(b) do
		if type(factor) == 'number' then
			c[factor] = c[factor] or 0
			c[factor] = c[factor] + power
			if c[factor] == 0 then
				c[factor] = nil
			end
		end
	end
	c.sign = a.sign * b.sign
	return c
end

-- compute 1/a for a rational number a; integers are also allowed
function p.inv(a)
	if type(a) == 'number' then
		a = p.new(a)
	end
	-- special case: NaN
	if a.nan then
		return { nan = true }
	end
	-- special case: infinity
	if a.inf then
		return { zero = true, sign = a.sign }
	end
	-- special case: zero
	if a.zero then
		return { inf = true, sign = a.sign }
	end
	-- regular case: not NaN, not infinity, not zero
	b = {}
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			b[factor] = -power
		end
	end
	b.sign = a.sign
	return b
end

-- divide a rational number a by b; integers are also allowed
function p.div(a, b)
	return p.mul(a, p.inv(b))
end

-- compute a^b; b must be an integer
function p.pow(a, b)
	if type(a) == 'number' then
		a = p.new(a)
	end
	if type(b) ~= 'number' then
		return nil
	end
	if a.nan then
		return { nan = true }
	end
	if a.inf then
		if b == 0 then
			return { nan = true }
		elseif b > 0 then
			return { inf = true, sign = math.pow(a.sign, b) }
		else
			return { zero = true, sign = math.pow(a.sign, b) }
		end
	end
	if a.zero then
		if b == 0 then
			return p.new(1)
		elseif b > 0 then
			return { zero = true, sign = math.pow(a.sign, b) }
		else
			return { inf = true, sign = math.pow(a.sign, b) }
		end
	end
	local c = p.new(1)
	for i = 1, math.abs(b) do
		if b > 0 then
			c = p.mul(c, a)
		else
			c = p.div(c, a)
		end
	end
	return c
end

-- compute a canonical representation of `a` modulo powers of `b`
function p.modulo_mul(a, b)
	if type(a) == 'number' then
		a = p.new(a)
	end
	if type(b) == 'number' then
		b = p.new(b)
	end
	if a.nan or b.nan or a.inf or b.inf or a.zero or b.zero then
		return p.copy(a)
	end
	local neg_power = -1/0
	local pos_power = 1/0
	for factor, power in pairs(b) do
		if type(factor) == 'number' then
			if (power > 0 and (a[factor] or 0) >= 0) or (power < 0 and (a[factor] or 0) <= 0) then
				pos_power = math.min(pos_power,
					math.floor((a[factor] or 0) / power)
				)
			else
				neg_power = math.max(neg_power,
					-math.ceil(math.abs(a[factor] or 0) / math.abs(power))
				)
			end
		end
	end
	local power = 0
	if neg_power ~= neg_power + 1 and neg_power < 0 then
		power = neg_power
	end
	if pos_power ~= pos_power + 1 and pos_power > 0 then
		power = pos_power
	end
	return p.div(a, p.pow(b, power))
end

-- add two rational numbers; integers are also allowed
function p.add(a, b)
	if type(a) == 'number' then
		a = p.new(a)
	end
	if type(b) == 'number' then
		b = p.new(b)
	end
	
	-- special case: NaN
	if a.nan or b.nan then
		return { nan = true }
	end
	-- special case: infinities
	if a.inf and b.inf then
		if a.sign == b.sign then
			return { inf = true, sign = a.sign }
		else
			return { nan = true }
		end
	end
	if a.inf then
		return { inf = true, sign = a.sign }
	end
	if b.inf then
		return { inf = true, sign = b.sign }
	end
	-- special case: one is zero
	if a.zero then
		return p.copy(b)
	end
	if b.zero then
		return p.copy(a)
	end
	-- regular case: both not NaN, not infinities, not zeros
	local gcd = { sign = 1 }
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			if math.min(power, b[factor] or 0) > 0 then
				gcd[factor] = math.min(power, b[factor])
			end
			if math.max(power, b[factor] or 0) < 0 then
				gcd[factor] = math.max(power, b[factor])
			end
		end
	end
	local a = p.div(a, gcd)
	local b = p.div(b, gcd)
	
	n_a, m_a = p.as_pair(a)
	n_b, m_b = p.as_pair(b)
	
	n = n_a * m_b + n_b * m_a
	m = m_a * m_b
	
	return p.mul(p.new(n, m), gcd)
end

-- substract a rational number from another; integers are also allowed
function p.sub(a, b)
	return p.add(a, p.mul(b, -1))
end

-- absolute value of a rational number; integers are also allowed
function p.abs(a)
	if a.nan then
		return { nan = true }
	end
	local b = p.copy(a)
	b.sign = 1
	return b
end

-- determine whether a rational number is less than another; integers are also allowed
function p.lt(a, b)
	local c = p.sub(a, b)
	if c.zero then
		return false
	else
		return c.sign == -1
	end
end

-- determine whether a rational number is less or equal to the other; integers are also allowed
function p.leq(a, b)
	local c = p.sub(a, b)
	if c.zero then
		return true
	else
		return c.sign == -1
	end
end

-- determine whether a rational number is greater than another; integers are also allowed
function p.gt(a, b)
	local c = p.sub(a, b)
	if c.zero then
		return false
	else
		return c.sign == 1
	end
end

-- determine whether a rational number is greater or equal to the other; integers are also allowed
function p.geq(a, b)
	local c = p.sub(a, b)
	if c.zero then
		return true
	else
		return c.sign == 1
	end
end

-- determine whether a rational number is equal to another; integers are also allowed
function p.eq(a, b)
	local c = p.sub(a, b)
	return c.zero
end

-- determine whether a rational number is integer
function p.is_int(a)
	if type(a) == 'number' then
		return true
	end
	if a.nan then
		return false
	end
	if a.inf then
		return false
	end
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			if power < 0 then
				return false
			end
		end
	end
	return true
end

-- find max prime in ket notation
function p.max_prime(a)
	if type(a) == 'number' then
		a = p.new(a)
	end
	if a.nan or a.inf or a.zero then
		return nil
	end
	local max_factor = 0
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			if factor > max_factor then
				max_factor = factor
			end
		end
	end
	return max_factor
end

-- determine whether the rational number is +- p/q, where p, q are primes OR 1
function p.is_prime_ratio(a)
	if type(a) == 'number' then
		a = p.new(a)
	end
	if a.nan or a.inf or a.zero then
		return false
	end
	local n_factors = 0
	local m_factors = 0
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			if power > 0 then
				n_factors = n_factors + 1
			else
				m_factors = m_factors + 1
			end
		end
	end
	return n_factors <= 1 and m_factors <= 1
end

-- return the (n, m) pair as a Lua tuple
function p.as_pair(a)
	if type(a) == 'number' then
		a = p.new(a)
	end
	-- special case: NaN
	if a.nan then
		return 0, 0
	end
	-- special case: infinity
	if a.inf then
		return a.sign, 0
	end
	-- special case: zero
	if a.zero then
		return 0, a.sign
	end
	-- regular case: not NaN, not infinity, not zero
	local n = 1
	local m = 1
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			if power > 0 then
				n = n * (factor ^ power)
			else
				m = m * (factor ^ (-power))
			end
		end
	end
	n = n * a.sign
	return n, m
end

-- return a string ratio representation
function p.as_ratio(a, separator)
	separator = separator or '/'
	local n, m = p.as_pair(a)
	return n .. separator .. m
end

-- return the {n, m} pair as a Lua table
function p.as_table(a)
	return {p.as_pair(a)}
end

-- return n / m as a float approximation
function p.as_float(a)
	local n, m = p.as_pair(a)
	return n / m
end

-- return a rational number in ket notation
-- NaN, infinity, zero values use special representations
function p.as_ket(a, frame)
	if type(a) == 'number' then
		a = p.new(a)
	end
	-- special case: NaN
	if a.nan then
		return 'NaN'
	end
	-- special case: infinity
	if a.inf then
		local sign = '+'
		if a.sign < 0 then
			sign = '-'
		end
		return sign .. '∞'
	end
	-- special case: zero
	if a.zero then
		return '0'
	end
	-- regular case: not NaN, not infinity, not zero
	
	local s = ''
	if a.sign < 0 then
		s = s .. '-'
	end
	-- preparing the argument
	local largest_prime = -1
	for factor, power in pairs(a) do
		if type(factor) == 'number' then
			if factor > largest_prime then
				largest_prime = factor
			end
		end
	end
	local template_arg = ''
	for i = 2, largest_prime do
		if u.is_prime(i) then
			if i > 2 then template_arg = template_arg .. ' ' end
			template_arg = template_arg .. (a[i] or 0)
		end
	end
	s = s .. frame:expandTemplate{
		title = 'Monzo',
		args = {template_arg}
	}
	return s
end

-- parse a rational number
-- returns nil on failure
function p.parse(unparsed)
	if type(unparsed) ~= 'string' then
		return nil
	end
	-- rational form
	local sign, n, _m, m = unparsed:match('^%s*(%-?)%s*(%d+)%s*(/%s*(%d+))%s*$')
	if n == nil then
		-- integer form
		sign, n = unparsed:match('^%s*(%-?)%s*(%d+)%s*$')
		if n == nil then
			-- parsing failure
			return nil
		else
			m = 1
			n = tonumber(n)
			if #sign > 0 then
				n = -n
			end
		end
	else
		n = tonumber(n)
		m = tonumber(m)
		if #sign > 0 then
			n = -n
		end
	end
	return p.new(n, m)
end

-- a version of as_ket() that can be {{#invoke:}}d
function p.ket(frame)
	local unparsed = frame.args[1] or '1'
	local a = p.parse(unparsed)
	if a == nil then
		return '<span style="color:red;">Invalid rational number: ' .. unparsed .. '.</span>'
	end
	return p.as_ket(a, frame)
end
p.monzo = p.ket

return p