Module:Pandorian rin
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Pandorian rin/doc
local p = {}
-- Config: exchange rates (per 1 RIN)
local JPY_PER_RIN = 2.33
local USD_PER_RIN = 0.0182
-- Units table in 10^3 steps
local UNITS = {
{name = "", pow = 0},
{name = "thousand", pow = 3},
{name = "million", pow = 6},
{name = "billion", pow = 9},
{name = "trillion", pow = 12},
{name = "quadrillion", pow = 15},
}
-- Quick lookup for worded units in input
local UNIT_INDEX = {
["thousand"] = 2,
["million"] = 3,
["billion"] = 4,
["trillion"] = 5,
["quadrillion"] = 6,
}
local function lang()
return mw.getContentLanguage() or mw.language.getContentLanguage()
end
-- Trim, remove commas/spaces around numbers
local function normalize_numstr(s)
s = mw.text.trim(s or "")
-- allow commas, thin spaces, etc.
s = s:gsub("[,%s ]", "")
return s
end
-- Round a number to N significant figures, return number
local function round_sig(x, sig)
if x == 0 or x == nil then return 0 end
local m = 10 ^ (sig - math.ceil(math.log10(math.abs(x))))
return math.floor(x * m + 0.5) / m
end
-- Format integer part with periods as thousand separators
local function add_period_grouping(numstr)
local sign, int, frac = numstr:match("^([%-]?)(%d+)(.*)$")
local rev = int:reverse()
local grouped = rev:gsub("(%d%d%d)", "%1.")
grouped = grouped:reverse()
-- Remove possible trailing dot if length was exact multiple of 3
grouped = grouped:gsub("^%.", "")
grouped = grouped:gsub("%.$", "")
return sign .. grouped .. (frac or "")
end
-- Period-separated formatting for RIN only
local function format_clean_rin(n)
local s = tostring(n)
if s:find("[eE]") then
s = string.format("%.15f", n)
end
if s:find("%.") then
s = s:gsub("0+$", ""):gsub("%.$", "")
end
local integer, frac = s:match("^([%-]?%d+)(%.%d+)?$")
if integer then
local grouped = add_period_grouping(integer)
if frac then
return grouped .. frac
else
return grouped
end
else
return s
end
end
-- Format a number without trailing zeros (but keep decimals if present)
local function format_clean(n)
-- Keep as plain string; language formatter for groupings:
local s = tostring(n)
-- If scientific notation appears, convert to plain:
if s:find("[eE]") then
-- rely on string.format; 15 sig figs safety
s = string.format("%.15f", n)
end
-- Trim trailing zeros after decimal
if s:find("%.") then
s = s:gsub("0+$", ""):gsub("%.$", "")
end
-- Apply language digit grouping when appropriate.
-- We only group integer part; leave decimals intact.
local integer, frac = s:match("^([%-]?%d+)(%.%d+)?$")
if integer then
local grouped = add_period_grouping(integer)
if frac then
return grouped .. frac
else
return grouped
end
else
-- fallback if parsing failed
return s
end
end
-- Choose a unit so value is in [1, 1000) whenever possible.
-- Returns scaled_value, unit_name
local function choose_unit(value)
if value == 0 then return 0, "" end
-- find best index
local absval = math.abs(value)
local idx = 1
-- push up while >= 1000 and we have higher units
while absval >= 1000 and idx < #UNITS do
absval = absval / 1000
idx = idx + 1
end
-- push down while < 1 and we have lower units
while absval < 1 and idx > 1 do
absval = absval * 1000
idx = idx - 1
end
local scaled = (value / (10 ^ UNITS[idx].pow))
return scaled, UNITS[idx].name
end
-- Parse the input amount parameter. Supports:
-- "398.1 million", "1.234 billion", "300.25", etc.
-- Returns:
-- rin_value (in base RIN, numeric),
-- display_amount (number to display on left),
-- display_unit (string unit to display on left),
-- used_worded_unit (bool, to control 4-sig-fig behavior)
local function parse_amount(param1)
local raw = mw.text.trim(param1 or "")
if raw == "" then
return 0, 0, "", false
end
-- Detect any unit word present
local unit_word = nil
local lower = mw.ustring.lower(raw)
for word, idx in pairs(UNIT_INDEX) do
if mw.ustring.find(lower, word) then
unit_word = word
break
end
end
-- Extract the numeric part (first number found)
local numstr = raw:match("([%d%.,%s %-]+)") or raw
numstr = normalize_numstr(numstr)
local num = tonumber(numstr)
if not num then
-- Could not parse; treat as 0
return 0, 0, "", false
end
local pow = 0
if unit_word then
pow = UNITS[UNIT_INDEX[unit_word]].pow
end
local rin_value = num * (10 ^ pow)
-- For display on the left:
local display_amount = num
local display_unit = unit_word or ""
-- If worded unit is used, clamp left-side amount to 4 sig figs
if unit_word then
display_amount = round_sig(display_amount, 4)
end
return rin_value, display_amount, display_unit or "", unit_word ~= nil
end
-- Format a (value, unit) pair with 4 significant figures and a unit word.
local function format_with_unit(value)
local scaled, uname = choose_unit(value)
local rounded = round_sig(scaled, 4)
return format_clean(rounded), uname
end
-- Build the left-side RIN string
local function build_left(link, display_amount, display_unit)
local star
if link == "yes" then
star = "[[Pandorian rin|✧]]"
else
star = "✧"
end
local amt = format_clean_rin(display_amount)
local unit_suffix = (display_unit ~= "" and (" " .. display_unit) or "")
return string.format('<span style="white-space: nowrap">%s%s%s</span> RIN',
star, amt ~= "" and amt or "0", unit_suffix)
end
-- Build conversion strings
local function build_conversions(rin_value, mode)
if mode ~= "yes" and mode ~= "USD" and mode ~= "JPY" then
return ""
end
local parts = {}
local function push(s) table.insert(parts, s) end
if mode == "yes" or mode == "JPY" then
local jpy_val = rin_value * JPY_PER_RIN
local jpy_amt, jpy_unit = format_with_unit(jpy_val)
local unit_part = (jpy_unit ~= "" and (" " .. jpy_unit) or "")
push("~JP¥" .. jpy_amt .. unit_part)
end
if mode == "yes" or mode == "USD" then
local usd_val = rin_value * USD_PER_RIN
local usd_amt, usd_unit = format_with_unit(usd_val)
local unit_part = (usd_unit ~= "" and (" " .. usd_unit) or "")
push("~US$" .. usd_amt .. unit_part)
end
if #parts == 0 then return "" end
return " ''(" .. table.concat(parts, " or ") .. ")''"
end
function p.rin(frame)
local args = frame:getParent() and frame:getParent().args or frame.args
local param1 = args[1]
local link = mw.text.trim(args["link"] or "no"):lower()
local conversion = mw.text.trim(args["conversion"] or "no"):upper() -- normalize to YES/USD/JPY
if conversion == "YES" then conversion = "yes" end
if conversion ~= "yes" and conversion ~= "USD" and conversion ~= "JPY" then
conversion = "no"
end
local rin_value, display_amount, display_unit = parse_amount(param1)
local left = build_left(link, display_amount, display_unit)
local right = build_conversions(rin_value, conversion)
-- Preserve your original HTML comment break before conversions
if right ~= "" then
return left .. "<!-- -->" .. right
else
return left
end
end
return p