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
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
-- regular case: both not NaN, not infinities
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.new(n, m)
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
-- 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
-- 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
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