Module:Rational: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Plumtree (talk | contribs)
m Bugfix
Plumtree (talk | contribs)
mNo edit summary
Line 33: Line 33:
end
end


-- copy a rational number
function p.copy(a)
function p.copy(a)
b = {}
b = {}
Line 327: Line 328:
end
end


-- return a string ratio representation
function p.as_ratio(a, separator)
function p.as_ratio(a, separator)
separator = separator or '/'
separator = separator or '/'

Revision as of 20:11, 2 October 2022

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 (23)
Line Function Params
5 new (n, m)
36 copy (a)
45 mul (a, b)
84 inv (a)
112 div (a, b)
117 add (a, b)
175 sub (a, b)
180 abs (a)
190 lt (a, b)
200 leq (a, b)
210 gt (a, b)
220 geq (a, b)
230 eq (a, b)
236 is_int (a)
257 max_prime (a)
276 is_prime_ratio (a)
298 as_pair (a)
331 as_ratio (a, separator)
338 as_table (a)
343 as_float (a)
350 as_ket (a, frame)
401 parse (unparsed)
431 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

-- 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