模組:PythonScript/Librarys/Decimal
外观
-- Module:PythonScript/Librarys/Decimal
-- 純 Lua BigFloat 引擎與 Python decimal 標準庫 (基於 BigInt 10^7 基底、1000 limbs 截斷與延遲載入架構)
local DecimalMod = {}
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
local MathCore = require("Module:PythonScript/Runtime/Cmath")
local BigInt = MathCore.BigInt
-- 集中管理內部狀態與方法,嚴格將頂層 local 變數控制在 6 個以內
local D = {
BASE = 10000000, -- 10^7 (每個 limb 7 位十進位數)
DIGITS_PER_LIMB = 7,
MAX_LIMBS = 1000, -- 有效數字上限:1000 個 limb (7000 位有效十進位數)
DEFAULT_FRAC_LIMBS = 10, -- 預設小數點後計算精度:10 個 limb (70 位小數)
initialized = false,
cache = {},
current_context = nil,
ln10_cache = nil,
ln10_prec = 0
}
-- ==========================================
-- 1. 基礎判斷與正規化核心 (BigFloat Engine)
-- ==========================================
function D.is_none(v)
return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end
function D.is_decimal(v)
return type(v) == "table" and v.__type == "instance" and v.__dict and v.__dict._is_decimal == true
end
function D.get_calc_frac_limbs(extra)
local prec = (D.current_context and D.current_context.__dict and D.current_context.__dict.prec) or 28
local needed = math.ceil(prec / 7) + 2
local res = math.max(D.DEFAULT_FRAC_LIMBS, needed) + (extra or 0)
if res > D.MAX_LIMBS then res = D.MAX_LIMBS end
return res
end
function D.raise_dec(exc_name, msg)
D.ensure_init()
local exc_cls = D.cache[exc_name] or Runtime.builtins.ArithmeticError
if D.current_context and D.current_context.__dict and D.current_context.__dict.flags then
local flags = D.current_context.__dict.flags
for _, p in ipairs(flags.pairs or {}) do
if type(p.key) == "table" and p.key.__name == exc_name then p.value = 1 end
end
end
Runtime.raise(exc_cls, msg or exc_name)
end
-- 正規化 BigFloat 結構: { sign = 1|-1, limbs = {...}, limb_exp = int, special = nil|"Inf"|"NaN"|"sNaN" }
function D.normalize_raw(sign, limbs, limb_exp, special)
sign = (sign and sign < 0) and -1 or 1
if special then return { sign = sign, limbs = {0}, limb_exp = 0, special = special } end
if not limbs or #limbs == 0 then return { sign = 1, limbs = {0}, limb_exp = 0 } end
local clean = {}
for i = 1, #limbs do clean[i] = limbs[i] or 0 end
while #clean > 1 and clean[#clean] == 0 do table.remove(clean) end
if #clean == 1 and clean[1] == 0 then return { sign = 1, limbs = {0}, limb_exp = 0 } end
limb_exp = limb_exp or 0
-- 若 limb_exp > 0 且展開後不超過 1000 個 limb,直接補齊低位 0 limb 使整數之 limb_exp 歸零
if limb_exp > 0 and (#clean + limb_exp) <= D.MAX_LIMBS then
local padded = {}
for i = 1, limb_exp do padded[i] = 0 end
for i = 1, #clean do padded[limb_exp + i] = clean[i] end
clean = padded
limb_exp = 0
end
-- 有效數字上限截斷:當 limb 數超過 1000 時,捨棄低位元 limb
if #clean > D.MAX_LIMBS then
local drop = #clean - D.MAX_LIMBS
local sliced = {}
for i = drop + 1, #clean do sliced[i - drop] = clean[i] end
clean = sliced
limb_exp = limb_exp + drop
while #clean > 1 and clean[#clean] == 0 do table.remove(clean) end
if #clean == 1 and clean[1] == 0 then return { sign = 1, limbs = {0}, limb_exp = 0 } end
end
-- 清除小數點後末尾全為 0 的 limb,確保所有整數皆滿足 limb_exp >= 0
if limb_exp < 0 then
local strip_cnt = 0
while strip_cnt < (#clean - 1) and (limb_exp + strip_cnt) < 0 and clean[strip_cnt + 1] == 0 do
strip_cnt = strip_cnt + 1
end
if strip_cnt > 0 then
local sliced = {}
for i = strip_cnt + 1, #clean do sliced[i - strip_cnt] = clean[i] end
clean = sliced
limb_exp = limb_exp + strip_cnt
end
end
return { sign = sign, limbs = clean, limb_exp = limb_exp }
end
function D.make_decimal_from_raw(raw, fixed_scale)
D.ensure_init()
local inst = PyClass.make_instance(D.DecimalClass)
inst.__dict._is_decimal = true
inst.__dict.sign = raw.sign or 1
inst.__dict.limbs = raw.limbs or {0}
inst.__dict.limb_exp = raw.limb_exp or 0
inst.__dict.special = raw.special
inst.__dict._fixed_scale = fixed_scale
return inst
end
function D.is_zero_raw(raw)
return not raw.special and #raw.limbs == 1 and raw.limbs[1] == 0
end
-- ==========================================
-- 2. 十進位字串解析與輸出轉換
-- ==========================================
function D.from_digits_and_exp(sign, digits, dec_exp)
digits = digits:gsub("^0+", "")
if digits == "" then return D.normalize_raw(1, {0}, 0) end
local trail_zeros = digits:match("(0+)$")
if trail_zeros and #trail_zeros < #digits then
digits = digits:sub(1, #digits - #trail_zeros)
dec_exp = dec_exp + #trail_zeros
end
local max_digits = D.MAX_LIMBS * 7
if #digits > max_digits then
local drop_d = #digits - max_digits
digits = digits:sub(1, max_digits)
dec_exp = dec_exp + drop_d
end
local limb_exp = math.floor(dec_exp / 7)
local rem_zeros = dec_exp - limb_exp * 7
if rem_zeros > 0 then digits = digits .. string.rep("0", rem_zeros) end
local limbs = BigInt.to_limbs(digits)
return D.normalize_raw(sign, limbs, limb_exp)
end
function D.parse_string(str)
if type(str) ~= "string" then D.raise_dec("ConversionSyntax", "Invalid literal for Decimal") end
local s = str:match("^%s*(.-)%s*$")
if s == "" then D.raise_dec("ConversionSyntax", "Invalid literal for Decimal: ''") end
local sign = 1
if s:sub(1, 1) == "-" then sign = -1; s = s:sub(2)
elseif s:sub(1, 1) == "+" then s = s:sub(2) end
local low = s:lower()
if low == "inf" or low == "infinity" then return D.normalize_raw(sign, {0}, 0, "Inf")
elseif low == "nan" or low:match("^nan%d*$") then return D.normalize_raw(sign, {0}, 0, "NaN")
elseif low == "snan" or low:match("^snan%d*$") then return D.normalize_raw(sign, {0}, 0, "sNaN") end
if s:find("__", 1, true) or s:sub(1, 1) == "_" or s:sub(-1) == "_" then
D.raise_dec("ConversionSyntax", "Invalid literal for Decimal: '" .. str .. "'")
end
s = s:gsub("(%d)_(%d)", "%1%2"):gsub("(%d)_(%d)", "%1%2")
local mantissa, exp_part = s:match("^([^eE]+)[eE]([%.%+%-]?%d+)$")
local e10 = 0
if mantissa then
local exp_num = tonumber(exp_part)
if not exp_num or math.floor(exp_num) ~= exp_num then
D.raise_dec("ConversionSyntax", "Invalid literal for Decimal: '" .. str .. "'")
end
e10 = exp_num
s = mantissa
end
local int_p, frac_p
if s:find("%.", 1, false) then
int_p, frac_p = s:match("^(%d*)%.(%d*)$")
if not int_p or (int_p == "" and frac_p == "") then
D.raise_dec("ConversionSyntax", "Invalid literal for Decimal: '" .. str .. "'")
end
else
if not s:match("^%d+$") then D.raise_dec("ConversionSyntax", "Invalid literal for Decimal: '" .. str .. "'") end
int_p, frac_p = s, ""
end
return D.from_digits_and_exp(sign, int_p .. frac_p, e10 - #frac_p)
end
function D.coerce_to_raw(val, allow_float)
if D.is_none(val) then
return D.normalize_raw(1, {0}, 0)
elseif D.is_decimal(val) then
return { sign = val.__dict.sign, limbs = val.__dict.limbs, limb_exp = val.__dict.limb_exp, special = val.__dict.special }
elseif type(val) == "boolean" then
return D.normalize_raw(1, { val and 1 or 0 }, 0)
elseif type(val) == "number" then
if not allow_float and math.floor(val) ~= val then
Runtime.raise("TypeError", "Cannot implicitly convert float to Decimal. Use Decimal.from_float() or Decimal(str(x))")
end
if val ~= val then return D.normalize_raw(1, {0}, 0, "NaN")
elseif val == math.huge then return D.normalize_raw(1, {0}, 0, "Inf")
elseif val == -math.huge then return D.normalize_raw(-1, {0}, 0, "Inf")
elseif math.floor(val) == val and math.abs(val) <= 9007199254740990 then
if val == 0 then return D.normalize_raw(1, {0}, 0) end
return D.normalize_raw(val < 0 and -1 or 1, BigInt.to_limbs(math.abs(val)), 0)
else
return D.parse_string(string.format("%.17g", val))
end
elseif type(val) == "table" and val.__type == "int" then
return D.normalize_raw(val.sign, val.limbs, 0)
elseif type(val) == "string" then
return D.parse_string(val)
elseif type(val) == "table" and (val.__type == "tuple" or val.__type == "list" or (val.__dict and val.__dict._is_dec_tuple)) then
local elts = val.elements or (val.__dict and val.__dict._elts)
if not elts or #elts ~= 3 then Runtime.raise("ValueError", "Invalid tuple size for Decimal construction") end
local s_bit, d_seq, exp_v = elts[1], elts[2], elts[3]
if (s_bit ~= 0 and s_bit ~= 1) or type(d_seq) ~= "table" then
Runtime.raise("ValueError", "Invalid tuple for Decimal construction")
end
local sign = (s_bit == 1) and -1 or 1
if exp_v == "F" then return D.normalize_raw(sign, {0}, 0, "Inf")
elseif exp_v == "n" then return D.normalize_raw(sign, {0}, 0, "NaN")
elseif exp_v == "N" then return D.normalize_raw(sign, {0}, 0, "sNaN") end
local d_elts = d_seq.elements or {}
local d_chars = {}
for i = 1, #d_elts do
local dg = d_elts[i]
if type(dg) ~= "number" or dg < 0 or dg > 9 or math.floor(dg) ~= dg then
Runtime.raise("ValueError", "Invalid digit in Decimal tuple")
end
d_chars[i] = tostring(dg)
end
local d_str = (#d_chars > 0) and table.concat(d_chars) or "0"
return D.from_digits_and_exp(sign, d_str, tonumber(exp_v) or 0)
end
return nil
end
function D.to_digits_and_exp(raw)
if D.is_zero_raw(raw) then return "0", 0 end
local raw_digits = BigInt.to_string(1, raw.limbs)
local dec_exp = raw.limb_exp * 7
local trail_zeros = raw_digits:match("(0+)$")
if trail_zeros and #trail_zeros < #raw_digits then
raw_digits = raw_digits:sub(1, #raw_digits - #trail_zeros)
dec_exp = dec_exp + #trail_zeros
end
return raw_digits, dec_exp
end
function D.round_to_exp(sign, raw_digits, dec_exp, target_exp, rounding_mode)
if dec_exp >= target_exp then return raw_digits, dec_exp end
rounding_mode = rounding_mode or (D.current_context and D.current_context.__dict.rounding) or "ROUND_HALF_EVEN"
local drop_count = target_exp - dec_exp
local kept_str, first_drop, rest_nonzero, any_nonzero, last_kept
if drop_count < #raw_digits then
kept_str = raw_digits:sub(1, #raw_digits - drop_count)
local dropped_str = raw_digits:sub(#raw_digits - drop_count + 1)
first_drop = tonumber(dropped_str:sub(1, 1)) or 0
rest_nonzero = dropped_str:sub(2):find("[1-9]") ~= nil
any_nonzero = (first_drop > 0) or rest_nonzero
last_kept = tonumber(kept_str:sub(-1)) or 0
elseif drop_count == #raw_digits then
kept_str = "0"
first_drop = tonumber(raw_digits:sub(1, 1)) or 0
rest_nonzero = raw_digits:sub(2):find("[1-9]") ~= nil
any_nonzero = (first_drop > 0) or rest_nonzero
last_kept = 0
else
kept_str = "0"
first_drop = 0
any_nonzero = (raw_digits ~= "0")
rest_nonzero = any_nonzero
last_kept = 0
end
local inc = false
if rounding_mode == "ROUND_DOWN" then inc = false
elseif rounding_mode == "ROUND_UP" then inc = any_nonzero
elseif rounding_mode == "ROUND_CEILING" then inc = any_nonzero and (sign > 0)
elseif rounding_mode == "ROUND_FLOOR" then inc = any_nonzero and (sign < 0)
elseif rounding_mode == "ROUND_HALF_UP" then inc = (first_drop >= 5)
elseif rounding_mode == "ROUND_HALF_DOWN" then inc = (first_drop > 5) or (first_drop == 5 and rest_nonzero)
elseif rounding_mode == "ROUND_05UP" then inc = any_nonzero and (last_kept == 0 or last_kept == 5)
else inc = (first_drop > 5) or (first_drop == 5 and (rest_nonzero or (last_kept % 2 == 1))) end
if inc then
if kept_str == "0" then kept_str = "1"
else kept_str = BigInt.to_string(1, BigInt.add_limbs(BigInt.to_limbs(kept_str), {1})) end
end
if kept_str == "0" then return "0", target_exp end
return kept_str, target_exp
end
function D.format_decimal(self, apply_prec, sci_style)
local d = self.__dict
if d.special == "Inf" then return (d.sign < 0 and "-Infinity" or "Infinity")
elseif d.special == "NaN" then return (d.sign < 0 and "-NaN" or "NaN")
elseif d.special == "sNaN" then return (d.sign < 0 and "-sNaN" or "sNaN") end
local sign_prefix = (d.sign < 0 and not D.is_zero_raw(d)) and "-" or ""
local raw_digits, dec_exp = D.to_digits_and_exp(d)
if d._fixed_scale ~= nil and not sci_style then
local scale = d._fixed_scale
local target_exp = -scale
local rounding = (D.current_context and D.current_context.__dict.rounding) or "ROUND_HALF_EVEN"
raw_digits, dec_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, target_exp, rounding)
if raw_digits == "0" then sign_prefix = "" end
if scale <= 0 then return sign_prefix .. raw_digits .. string.rep("0", math.max(0, dec_exp)) end
if dec_exp > target_exp then
raw_digits = raw_digits .. string.rep("0", dec_exp - target_exp)
dec_exp = target_exp
end
if #raw_digits > scale then
return sign_prefix .. raw_digits:sub(1, #raw_digits - scale) .. "." .. raw_digits:sub(#raw_digits - scale + 1)
else
return sign_prefix .. "0." .. string.rep("0", scale - #raw_digits) .. raw_digits
end
end
if raw_digits == "0" then return "0" end
if sci_style then
local prec = (D.current_context and D.current_context.__dict.prec) or 28
local rounding = (D.current_context and D.current_context.__dict.rounding) or "ROUND_HALF_EVEN"
if #raw_digits > prec then
raw_digits, dec_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, dec_exp + (#raw_digits - prec), rounding)
local z = raw_digits:match("(0+)$")
if z and #z < #raw_digits then raw_digits = raw_digits:sub(1, #raw_digits - #z); dec_exp = dec_exp + #z end
end
local adj_exp = dec_exp + #raw_digits - 1
local e_char = (D.current_context and D.current_context.__dict.capitals == 0) and "e" or "E"
if #raw_digits == 1 then
return sign_prefix .. raw_digits .. e_char .. (adj_exp >= 0 and "+" or "") .. tostring(adj_exp)
else
return sign_prefix .. raw_digits:sub(1, 1) .. "." .. raw_digits:sub(2) .. e_char .. (adj_exp >= 0 and "+" or "") .. tostring(adj_exp)
end
end
if dec_exp >= 0 then
if (#raw_digits + dec_exp) <= (D.MAX_LIMBS * 7) then
return sign_prefix .. raw_digits .. string.rep("0", dec_exp)
else
return D.format_decimal(self, apply_prec, true)
end
end
if apply_prec ~= false then
local prec = (D.current_context and D.current_context.__dict.prec) or 28
local rounding = (D.current_context and D.current_context.__dict.rounding) or "ROUND_HALF_EVEN"
if (-dec_exp) > prec then
raw_digits, dec_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, -prec, rounding)
if raw_digits == "0" then return "0" end
local z = raw_digits:match("(0+)$")
if z and #z < #raw_digits then raw_digits = raw_digits:sub(1, #raw_digits - #z); dec_exp = dec_exp + #z end
end
end
if dec_exp >= 0 then return sign_prefix .. raw_digits .. string.rep("0", dec_exp) end
local frac_len = -dec_exp
if #raw_digits > frac_len then
return sign_prefix .. raw_digits:sub(1, #raw_digits - frac_len) .. "." .. raw_digits:sub(#raw_digits - frac_len + 1)
else
return sign_prefix .. "0." .. string.rep("0", frac_len - #raw_digits) .. raw_digits
end
end
-- ==========================================
-- 3. 四則運算與比較核心 (BigFloat Operations)
-- ==========================================
function D.cmp_raw(a, b)
if a.special == "NaN" or a.special == "sNaN" or b.special == "NaN" or b.special == "sNaN" then return nil end
if a.special == "Inf" or b.special == "Inf" then
if a.special == "Inf" and b.special == "Inf" then
if a.sign == b.sign then return 0 end
return a.sign < b.sign and -1 or 1
elseif a.special == "Inf" then return a.sign > 0 and 1 or -1
else return b.sign > 0 and -1 or 1 end
end
local za, zb = D.is_zero_raw(a), D.is_zero_raw(b)
if za and zb then return 0 end
if za then return b.sign > 0 and -1 or 1 end
if zb then return a.sign > 0 and 1 or -1 end
if a.sign ~= b.sign then return a.sign < b.sign and -1 or 1 end
local top_a = #a.limbs + a.limb_exp
local top_b = #b.limbs + b.limb_exp
local mag_cmp = 0
if top_a ~= top_b then
mag_cmp = (top_a < top_b) and -1 or 1
else
local i, j = #a.limbs, #b.limbs
while i >= 1 or j >= 1 do
local va = (i >= 1) and a.limbs[i] or 0
local vb = (j >= 1) and b.limbs[j] or 0
if va ~= vb then mag_cmp = (va < vb) and -1 or 1; break end
i = i - 1; j = j - 1
end
end
return (a.sign > 0) and mag_cmp or -mag_cmp
end
function D.align_limbs(a, b)
if a.limb_exp == b.limb_exp then return a.limbs, b.limbs, a.limb_exp end
local high, low, swapped = a, b, false
if a.limb_exp < b.limb_exp then high, low, swapped = b, a, true end
local diff = high.limb_exp - low.limb_exp
local low_limbs = low.limbs
local base_exp = low.limb_exp
local total_span = #high.limbs + diff
if total_span > (D.MAX_LIMBS + 5) then
local trim = total_span - (D.MAX_LIMBS + 5)
if trim >= #low_limbs then
low_limbs = {0}; diff = 0; base_exp = high.limb_exp
else
local sliced = {}
for i = trim + 1, #low_limbs do sliced[i - trim] = low_limbs[i] end
low_limbs = sliced; diff = diff - trim; base_exp = base_exp + trim
end
end
local high_shifted = {}
for i = 1, diff do high_shifted[i] = 0 end
for i = 1, #high.limbs do high_shifted[diff + i] = high.limbs[i] end
if swapped then return low_limbs, high_shifted, base_exp
else return high_shifted, low_limbs, base_exp end
end
function D.add_raw(a, b)
if a.special == "sNaN" or b.special == "sNaN" then D.raise_dec("InvalidOperation", "operation on sNaN") end
if a.special == "NaN" or b.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
if a.special == "Inf" or b.special == "Inf" then
if a.special == "Inf" and b.special == "Inf" then
if a.sign ~= b.sign then D.raise_dec("InvalidOperation", "-INF + INF") end
return D.normalize_raw(a.sign, {0}, 0, "Inf")
end
return D.normalize_raw(a.special == "Inf" and a.sign or b.sign, {0}, 0, "Inf")
end
if D.is_zero_raw(a) then return D.normalize_raw(b.sign, b.limbs, b.limb_exp) end
if D.is_zero_raw(b) then return D.normalize_raw(a.sign, a.limbs, a.limb_exp) end
local la, lb, common_exp = D.align_limbs(a, b)
local res_sign, res_limbs
if a.sign == b.sign then
res_sign = a.sign; res_limbs = BigInt.add_limbs(la, lb)
else
local cmp = BigInt.cmp_limbs(la, lb)
if cmp == 0 then return D.normalize_raw(1, {0}, 0)
elseif cmp > 0 then res_sign = a.sign; res_limbs = BigInt.sub_limbs(la, lb)
else res_sign = b.sign; res_limbs = BigInt.sub_limbs(lb, la) end
end
return D.normalize_raw(res_sign, res_limbs, common_exp)
end
function D.sub_raw(a, b)
return D.add_raw(a, { sign = -b.sign, limbs = b.limbs, limb_exp = b.limb_exp, special = b.special })
end
function D.mul_raw(a, b)
if a.special == "sNaN" or b.special == "sNaN" then D.raise_dec("InvalidOperation", "operation on sNaN") end
if a.special == "NaN" or b.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
local res_sign = a.sign * b.sign
if a.special == "Inf" or b.special == "Inf" then
if D.is_zero_raw(a) or D.is_zero_raw(b) then D.raise_dec("InvalidOperation", "0 * INF") end
return D.normalize_raw(res_sign, {0}, 0, "Inf")
end
if D.is_zero_raw(a) or D.is_zero_raw(b) then return D.normalize_raw(1, {0}, 0) end
return D.normalize_raw(res_sign, BigInt.mul_limbs(a.limbs, b.limbs), a.limb_exp + b.limb_exp)
end
function D.div_raw(a, b, extra_limbs)
if a.special == "sNaN" or b.special == "sNaN" then D.raise_dec("InvalidOperation", "operation on sNaN") end
if a.special == "NaN" or b.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
local res_sign = a.sign * b.sign
if a.special == "Inf" and b.special == "Inf" then D.raise_dec("DivisionUndefined", "INF / INF")
elseif D.is_zero_raw(a) and D.is_zero_raw(b) then D.raise_dec("DivisionUndefined", "0 / 0")
elseif D.is_zero_raw(b) then D.raise_dec("DivisionByZero", "division by zero")
elseif b.special == "Inf" then return D.normalize_raw(1, {0}, 0)
elseif a.special == "Inf" then return D.normalize_raw(res_sign, {0}, 0, "Inf")
elseif D.is_zero_raw(a) then return D.normalize_raw(1, {0}, 0) end
local frac_target = D.get_calc_frac_limbs(extra_limbs)
local q, r = BigInt.divmod_limbs(a.limbs, b.limbs)
local cur_limb_exp = a.limb_exp - b.limb_exp
local sig_limbs = (#q == 1 and q[1] == 0) and 0 or #q
while not (#r == 1 and r[1] == 0) and (cur_limb_exp > -frac_target or sig_limbs < frac_target) and sig_limbs < D.MAX_LIMBS do
table.insert(r, 1, 0)
local lq
lq, r = BigInt.divmod_limbs(r, b.limbs)
local digit_limb = lq[1] or 0
if sig_limbs == 0 then
if digit_limb ~= 0 then q = { digit_limb }; sig_limbs = 1 end
else
table.insert(q, 1, digit_limb)
sig_limbs = sig_limbs + 1
end
cur_limb_exp = cur_limb_exp - 1
end
return D.normalize_raw(res_sign, q, cur_limb_exp)
end
function D.trunc_raw(a)
if a.special then D.raise_dec("InvalidOperation", "cannot truncate Infinity or NaN") end
if a.limb_exp >= 0 or D.is_zero_raw(a) then return D.normalize_raw(a.sign, a.limbs, a.limb_exp) end
local drop = -a.limb_exp
if drop >= #a.limbs then return D.normalize_raw(1, {0}, 0) end
local int_limbs = {}
for i = drop + 1, #a.limbs do int_limbs[i - drop] = a.limbs[i] end
return D.normalize_raw(a.sign, int_limbs, 0)
end
function D.divmod_raw(a, b)
if D.is_zero_raw(b) then
if D.is_zero_raw(a) then D.raise_dec("DivisionUndefined", "0 / 0") end
D.raise_dec("DivisionByZero", "division by zero")
end
if a.special or b.special then
if a.special == "Inf" or (b.special and b.special ~= "Inf") then
D.raise_dec("InvalidOperation", "divmod on non-finite Decimal")
end
if b.special == "Inf" then return D.normalize_raw(1, {0}, 0), D.normalize_raw(a.sign, a.limbs, a.limb_exp) end
end
local q_full = D.div_raw(a, b, 2)
local q_int = D.trunc_raw(q_full)
local rem = D.sub_raw(a, D.mul_raw(b, q_int))
return q_int, rem
end
-- ==========================================
-- 4. 進階數學函式 (sqrt, ln, log10, exp, pow)
-- ==========================================
function D.sqrt_raw(a, extra_limbs)
if a.special == "sNaN" then D.raise_dec("InvalidOperation", "sqrt on sNaN") end
if a.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
if a.sign < 0 and not D.is_zero_raw(a) then D.raise_dec("InvalidOperation", "sqrt of negative Decimal") end
if a.special == "Inf" then return D.normalize_raw(1, {0}, 0, "Inf") end
if D.is_zero_raw(a) then return D.normalize_raw(1, {0}, 0) end
local work_limbs = D.get_calc_frac_limbs(extra_limbs)
local target_exp = -work_limbs
if math.floor(a.limb_exp / 2) < target_exp then target_exp = math.floor(a.limb_exp / 2) - work_limbs end
local shift = a.limb_exp - 2 * target_exp
local n_limbs = {}
if shift >= 0 then
for i = 1, shift do n_limbs[i] = 0 end
for i = 1, #a.limbs do n_limbs[shift + i] = a.limbs[i] end
else
local drop = -shift
for i = drop + 1, #a.limbs do n_limbs[i - drop] = a.limbs[i] end
if #n_limbs == 0 then return D.normalize_raw(1, {0}, 0) end
end
local n_len = #n_limbs
local half_len = math.ceil(n_len / 2)
local x = {}
for i = 1, half_len do x[i] = 0 end
if n_len % 2 == 1 then
local top = n_limbs[n_len]
local nxt = (n_len >= 2) and n_limbs[n_len - 1] or 0
local nxt2 = (n_len >= 3) and n_limbs[n_len - 2] or 0
local comb = (top * D.BASE + nxt) * D.BASE + nxt2
local sq = math.floor(math.sqrt(comb))
x[half_len] = math.max(1, math.floor(sq / D.BASE))
if half_len >= 2 then x[half_len - 1] = sq % D.BASE end
else
local top = n_limbs[n_len] * D.BASE + n_limbs[n_len - 1]
local nxt = (n_len >= 3) and n_limbs[n_len - 2] or 0
local nxt2 = (n_len >= 4) and n_limbs[n_len - 3] or 0
local comb = (top * D.BASE + nxt) * D.BASE + nxt2
local sq = math.floor(math.sqrt(comb))
x[half_len] = math.max(1, math.floor(sq / D.BASE))
if half_len >= 2 then x[half_len - 1] = sq % D.BASE end
end
local two_limb = {2}
for _ = 1, 14 do
local q = BigInt.divmod_limbs(n_limbs, x)
local nx = BigInt.divmod_limbs(BigInt.add_limbs(x, q), two_limb)
local cmp = BigInt.cmp_limbs(nx, x)
if cmp == 0 then x = nx; break
elseif cmp > 0 then
local diff = BigInt.sub_limbs(nx, x)
if #diff == 1 and diff[1] <= 1 then x = nx; break end
else
local diff = BigInt.sub_limbs(x, nx)
if #diff == 1 and diff[1] <= 1 then x = nx; break end
end
x = nx
end
return D.normalize_raw(1, x, target_exp)
end
function D.ln_unit_raw(m_raw, work_limbs)
local one = D.normalize_raw(1, {1}, 0)
if D.cmp_raw(m_raw, one) == 0 then return D.normalize_raw(1, {0}, 0) end
local cur = m_raw
local k_roots = 5
for _ = 1, k_roots do cur = D.sqrt_raw(cur, 4) end
local y = D.div_raw(D.sub_raw(cur, one), D.add_raw(cur, one), 4)
local y2 = D.mul_raw(y, y)
local term, sum = y, y
local cutoff_exp = -(work_limbs + 3)
for n = 3, 99, 2 do
term = D.mul_raw(term, y2)
if D.is_zero_raw(term) or (#term.limbs + term.limb_exp) < cutoff_exp then break end
sum = D.add_raw(sum, D.div_raw(term, D.normalize_raw(1, {n}, 0), 4))
end
return D.mul_raw(sum, D.normalize_raw(1, { 2 * (2 ^ k_roots) }, 0))
end
function D.get_ln10_raw(work_limbs)
if D.ln10_cache and D.ln10_prec >= work_limbs then return D.ln10_cache end
local ln2 = D.ln_unit_raw(D.normalize_raw(1, {2}, 0), work_limbs + 2)
local ln5 = D.ln_unit_raw(D.normalize_raw(1, {5}, 0), work_limbs + 2)
D.ln10_cache = D.add_raw(ln2, ln5)
D.ln10_prec = work_limbs
return D.ln10_cache
end
function D.ln_raw(a)
if a.special == "sNaN" then D.raise_dec("InvalidOperation", "ln on sNaN") end
if a.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
if D.is_zero_raw(a) then return D.normalize_raw(-1, {0}, 0, "Inf") end
if a.sign < 0 then D.raise_dec("InvalidOperation", "ln of negative Decimal") end
if a.special == "Inf" then return D.normalize_raw(1, {0}, 0, "Inf") end
local raw_digits, dec_exp = D.to_digits_and_exp(a)
local k = (#raw_digits - 1) + dec_exp
local m_raw = D.from_digits_and_exp(1, raw_digits, -(#raw_digits - 1))
local work_limbs = D.get_calc_frac_limbs(3)
local ln_m = D.ln_unit_raw(m_raw, work_limbs)
if k == 0 then return ln_m end
local ln10 = D.get_ln10_raw(work_limbs)
local k_raw = D.normalize_raw(k < 0 and -1 or 1, BigInt.to_limbs(math.abs(k)), 0)
return D.add_raw(ln_m, D.mul_raw(k_raw, ln10))
end
function D.log10_raw(a)
if a.special == "sNaN" then D.raise_dec("InvalidOperation", "log10 on sNaN") end
if a.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
if D.is_zero_raw(a) then return D.normalize_raw(-1, {0}, 0, "Inf") end
if a.sign < 0 then D.raise_dec("InvalidOperation", "log10 of negative Decimal") end
if a.special == "Inf" then return D.normalize_raw(1, {0}, 0, "Inf") end
local raw_digits, dec_exp = D.to_digits_and_exp(a)
local k = (#raw_digits - 1) + dec_exp
if raw_digits == "1" then
return D.normalize_raw(k < 0 and -1 or 1, BigInt.to_limbs(math.abs(k)), 0)
end
local work_limbs = D.get_calc_frac_limbs(3)
local m_raw = D.from_digits_and_exp(1, raw_digits, -(#raw_digits - 1))
local ln_m = D.ln_unit_raw(m_raw, work_limbs)
local ln10 = D.get_ln10_raw(work_limbs)
local log10_m = D.div_raw(ln_m, ln10, 2)
local k_raw = D.normalize_raw(k < 0 and -1 or 1, BigInt.to_limbs(math.abs(k)), 0)
return D.add_raw(log10_m, k_raw)
end
function D.exp_raw(a)
if a.special == "sNaN" then D.raise_dec("InvalidOperation", "exp on sNaN") end
if a.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
if D.is_zero_raw(a) then return D.normalize_raw(1, {1}, 0) end
if a.special == "Inf" then
if a.sign > 0 then return D.normalize_raw(1, {0}, 0, "Inf")
else return D.normalize_raw(1, {0}, 0) end
end
local work_limbs = D.get_calc_frac_limbs(3)
local ln10 = D.get_ln10_raw(work_limbs)
local q_int, r = D.divmod_raw(a, ln10)
local k10 = tonumber(BigInt.to_string(q_int.sign, q_int.limbs)) or 0
local scale_pow = 6
local x = D.div_raw(r, D.normalize_raw(1, { 2 ^ scale_pow }, 0), 4)
local one = D.normalize_raw(1, {1}, 0)
local sum, term = one, one
local cutoff_exp = -(work_limbs + 3)
for n = 1, 60 do
term = D.div_raw(D.mul_raw(term, x), D.normalize_raw(1, {n}, 0), 4)
if D.is_zero_raw(term) or (#term.limbs + term.limb_exp) < cutoff_exp then break end
sum = D.add_raw(sum, term)
end
for _ = 1, scale_pow do sum = D.mul_raw(sum, sum) end
if k10 ~= 0 then sum = D.mul_raw(sum, D.from_digits_and_exp(1, "1", k10)) end
return sum
end
function D.pow_raw(a, b)
if a.special == "sNaN" or b.special == "sNaN" then D.raise_dec("InvalidOperation", "pow on sNaN") end
if a.special == "NaN" or b.special == "NaN" then return D.normalize_raw(1, {0}, 0, "NaN") end
if D.is_zero_raw(a) and D.is_zero_raw(b) then D.raise_dec("InvalidOperation", "0 ** 0") end
if D.is_zero_raw(b) then return D.normalize_raw(1, {1}, 0) end
if D.is_zero_raw(a) then
if b.sign < 0 then D.raise_dec("DivisionByZero", "0 ** negative") end
return D.normalize_raw(1, {0}, 0)
end
if not b.special and b.limb_exp >= 0 then
local exp_num = tonumber(BigInt.to_string(1, b.limbs))
if exp_num and exp_num <= 100000 then
local k = math.floor(exp_num)
local res = D.normalize_raw(1, {1}, 0)
local base = D.normalize_raw(a.sign, a.limbs, a.limb_exp)
while k > 0 do
if k % 2 == 1 then res = D.mul_raw(res, base) end
k = math.floor(k / 2)
if k > 0 then base = D.mul_raw(base, base) end
end
if b.sign < 0 then return D.div_raw(D.normalize_raw(1, {1}, 0), res) end
return res
end
end
if a.sign < 0 then D.raise_dec("InvalidOperation", "negative Decimal cannot be raised to a fractional power") end
return D.exp_raw(D.mul_raw(D.ln_raw(a), b))
end
-- ==========================================
-- 5. 供 math 模組呼叫的橋接輔助函式
-- ==========================================
function DecimalMod.to_number(dec)
local d = dec.__dict
if d.special == "Inf" then return d.sign < 0 and -math.huge or math.huge
elseif d.special == "NaN" or d.special == "sNaN" then return 0/0 end
local raw_digits, dec_exp = D.to_digits_and_exp(d)
if raw_digits == "0" then return 0.0 end
if #raw_digits > 20 then
local drop = #raw_digits - 20
raw_digits = raw_digits:sub(1, 20)
dec_exp = dec_exp + drop
end
local num = tonumber((d.sign < 0 and "-" or "") .. raw_digits .. "e" .. tostring(dec_exp))
return num or (d.sign < 0 and -math.huge or math.huge)
end
function DecimalMod.raw_to_py_int(raw)
local int_raw = D.trunc_raw(raw)
if D.is_zero_raw(int_raw) then return 0 end
local s = BigInt.to_string(int_raw.sign, int_raw.limbs)
if int_raw.limb_exp > 0 then s = s .. string.rep("0", int_raw.limb_exp * 7) end
return Runtime.builtins.int.__call({s})
end
function DecimalMod.math_trunc(dec) return DecimalMod.raw_to_py_int(dec.__dict) end
function DecimalMod.math_floor(dec)
local d = dec.__dict
local t = D.trunc_raw(d)
if d.limb_exp < 0 and d.sign < 0 then t = D.sub_raw(t, D.normalize_raw(1, {1}, 0)) end
return DecimalMod.raw_to_py_int(t)
end
function DecimalMod.math_ceil(dec)
local d = dec.__dict
local t = D.trunc_raw(d)
if d.limb_exp < 0 and d.sign > 0 then t = D.add_raw(t, D.normalize_raw(1, {1}, 0)) end
return DecimalMod.raw_to_py_int(t)
end
function DecimalMod.math_isqrt(val)
local raw = D.coerce_to_raw(val, true)
if raw.sign < 0 then Runtime.raise("ValueError", "isqrt() argument must be nonnegative") end
return DecimalMod.raw_to_py_int(D.sqrt_raw(D.trunc_raw(raw)))
end
local function compute_adjusted_ln(val)
local raw = D.coerce_to_raw(val, true)
if raw.special == "NaN" or raw.special == "sNaN" then return 0/0 end
if D.is_zero_raw(raw) or raw.sign < 0 then Runtime.raise("ValueError", "math domain error") end
if raw.special == "Inf" then return math.huge end
local raw_digits, dec_exp = D.to_digits_and_exp(raw)
local k = (#raw_digits - 1) + dec_exp
local m_num = tonumber(raw_digits:sub(1, 1) .. "." .. raw_digits:sub(2, 18)) or 1.0
return math.log(m_num) + k * math.log(10), m_num, k, (raw_digits == "1")
end
function DecimalMod.math_log(x, base)
local lnx = compute_adjusted_ln(x)
if base ~= nil and not D.is_none(base) then return lnx / compute_adjusted_ln(base) end
return lnx
end
function DecimalMod.math_log10(x)
local _, m_num, k, is_pow10 = compute_adjusted_ln(x)
if is_pow10 then return k * 1.0 end
return math.log10(m_num) + k
end
function DecimalMod.math_log2(x)
return compute_adjusted_ln(x) / math.log(2)
end
-- ==========================================
-- 6. 延遲初始化 Decimal、Context 與例外類別
-- ==========================================
function D.ensure_init()
if D.initialized then return end
D.initialized = true
local B = Runtime.builtins
local C = D.cache
-- [A] 捨入常數與模組常數
local round_modes = {
"ROUND_CEILING", "ROUND_DOWN", "ROUND_FLOOR", "ROUND_HALF_DOWN",
"ROUND_HALF_EVEN", "ROUND_HALF_UP", "ROUND_UP", "ROUND_05UP"
}
for _, rm in ipairs(round_modes) do C[rm] = rm end
C.MAX_PREC = D.MAX_LIMBS * 7
C.MAX_EMAX = 999999999
C.MIN_EMIN = -999999999
C.MIN_ETINY = -1999999997
C.HAVE_THREADS = false
C.HAVE_CONTEXTVAR = false
C.IEEE_CONTEXT_MAX_BITS = 512
C.__version__ = "1.70"
C.__libmpdec_version__ = "2.5.1"
-- [B] Decimal 例外繼承樹
local function make_dec_exc(name, bases)
local cls = PyClass.make_class(name, bases, {})
cls.__dict["__module__"] = "decimal"
C[name] = cls
return cls
end
local DecimalException = make_dec_exc("DecimalException", { B.ArithmeticError })
make_dec_exc("Clamped", { DecimalException })
local InvalidOperation = make_dec_exc("InvalidOperation", { DecimalException })
make_dec_exc("ConversionSyntax", { InvalidOperation })
make_dec_exc("DivisionImpossible", { InvalidOperation })
make_dec_exc("InvalidContext", { InvalidOperation })
make_dec_exc("DivisionByZero", { DecimalException, B.ZeroDivisionError })
make_dec_exc("DivisionUndefined", { InvalidOperation, B.ZeroDivisionError })
local Inexact = make_dec_exc("Inexact", { DecimalException })
local Rounded = make_dec_exc("Rounded", { DecimalException })
local Subnormal = make_dec_exc("Subnormal", { DecimalException })
make_dec_exc("Overflow", { Inexact, Rounded })
make_dec_exc("Underflow", { Inexact, Rounded, Subnormal })
make_dec_exc("FloatOperation", { DecimalException, B.TypeError })
-- [C] DecimalTuple 類別
local dt_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local s = (a[2] ~= nil and a[2]) or (kw and kw.sign) or 0
local dg = (a[3] ~= nil and a[3]) or (kw and kw.digits) or { __type = "tuple", elements = {0} }
local ex = (a[4] ~= nil and a[4]) or (kw and kw.exponent) or 0
if type(dg) == "table" and dg.__type == "list" then dg = { __type = "tuple", elements = dg.elements } end
self.__dict._is_dec_tuple = true
self.__dict.sign = s
self.__dict.digits = dg
self.__dict.exponent = ex
self.__dict._elts = { s, dg, ex }
return nil
end },
__getitem__ = { __type = "builtin_method", __call = function(a)
local self, idx = a[1], a[2]
if type(idx) == "number" then
if idx < 0 then idx = 3 + idx end
local v = self.__dict._elts[idx + 1]
if v ~= nil then return v end
end
Runtime.raise("IndexError", "tuple index out of range")
end },
__len__ = { __type = "builtin_method", __call = function() return 3 end },
__iter__ = { __type = "builtin_method", __call = function(a)
return B.iter({ { __type = "tuple", elements = a[1].__dict._elts } })
end },
__eq__ = { __type = "builtin_method", __call = function(a)
local self, other = a[1], a[2]
local other_elts = (type(other) == "table") and (other.elements or (other.__dict and other.__dict._elts))
if not other_elts or #other_elts ~= 3 then return false end
return require("Module:PythonScript/Evaluator/Utils").py_eq(
{ __type = "tuple", elements = self.__dict._elts },
{ __type = "tuple", elements = other_elts }
)
end },
__repr__ = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return string.format("DecimalTuple(sign=%s, digits=%s, exponent=%s)",
B.repr({d.sign}), B.repr({d.digits}), B.repr({d.exponent}))
end }
}
dt_dict.__str__ = dt_dict.__repr__
C.DecimalTuple = PyClass.make_class("DecimalTuple", {}, dt_dict)
C.DecimalTuple.__dict["__module__"] = "decimal"
-- [D] Decimal 主類別與方法庫
local function bin_op(self, other, fn)
local rb = D.coerce_to_raw(other, false)
if not rb then return B.NotImplemented end
return D.make_decimal_from_raw(fn(self.__dict, rb))
end
local function rbin_op(self, left, fn)
local ra = D.coerce_to_raw(left, false)
if not ra then return B.NotImplemented end
return D.make_decimal_from_raw(fn(ra, self.__dict))
end
local dec_dict = {
__new__ = { __type = "staticmethod", __func = { __type = "builtin_method", __name = "__new__", __call = function(a)
local cls = a[1]
local val = (a[2] ~= nil) and a[2] or 0
local raw = D.coerce_to_raw(val, true)
if not raw then Runtime.raise("TypeError", "Cannot convert " .. tostring(val) .. " to Decimal") end
local inst = D.make_decimal_from_raw(raw)
inst.__class = cls
return inst
end } },
__init__ = { __type = "builtin_method", __name = "__init__", __call = function() return nil end },
from_float = { __type = "classmethod", __func = { __type = "builtin_method", __name = "from_float", __call = function(a)
local cls, f = a[1], a[2]
if type(f) == "boolean" then f = f and 1 or 0 end
if type(f) ~= "number" and not (type(f) == "table" and f.__type == "int") then
Runtime.raise("TypeError", "argument must be int or float")
end
local inst = D.make_decimal_from_raw(D.coerce_to_raw(f, true))
inst.__class = cls
return inst
end } },
-- 字串、型別轉換與格式化
__str__ = { __type = "builtin_method", __call = function(a) return D.format_decimal(a[1], true, false) end },
__repr__ = { __type = "builtin_method", __call = function(a) return "Decimal('" .. D.format_decimal(a[1], true, false) .. "')" end },
__format__ = { __type = "builtin_method", __call = function(a)
local self, spec = a[1], a[2] or ""
if spec == "" then return D.format_decimal(self, true, false) end
local dot_prec, ftype = spec:match("^.*%.(%d+)([fFeEgG%%]?)$")
if dot_prec and (ftype == "f" or ftype == "F" or ftype == "") then
local n = tonumber(dot_prec)
local copy = D.make_decimal_from_raw(self.__dict, n)
return D.format_decimal(copy, false, false)
end
return B.format({ DecimalMod.to_number(self), spec })
end },
__bool__ = { __type = "builtin_method", __call = function(a) return not D.is_zero_raw(a[1].__dict) end },
__int__ = { __type = "builtin_method", __call = function(a) return DecimalMod.raw_to_py_int(a[1].__dict) end },
__trunc__ = { __type = "builtin_method", __call = function(a) return DecimalMod.raw_to_py_int(a[1].__dict) end },
__float__ = { __type = "builtin_method", __call = function(a) return DecimalMod.to_number(a[1]) end },
__hash__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
if self.__dict.special == "NaN" or self.__dict.special == "sNaN" then
Runtime.raise("TypeError", "Cannot hash a NaN value")
end
return B.hash({ DecimalMod.to_number(self) })
end },
__round__ = { __type = "builtin_method", __call = function(a)
local self, ndigits = a[1], a[2]
local d = self.__dict
if D.is_none(ndigits) then
local raw_digits, dec_exp = D.to_digits_and_exp(d)
local r_digits, r_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, 0, "ROUND_HALF_EVEN")
return DecimalMod.raw_to_py_int(D.from_digits_and_exp(d.sign, r_digits, r_exp))
end
local n = tonumber(ndigits) or 0
local raw_digits, dec_exp = D.to_digits_and_exp(d)
local r_digits, r_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, -n, "ROUND_HALF_EVEN")
return D.make_decimal_from_raw(D.from_digits_and_exp(d.sign, r_digits, r_exp), n > 0 and n or nil)
end },
-- 一元與二元算術運算
__pos__ = { __type = "builtin_method", __call = function(a) return D.make_decimal_from_raw(a[1].__dict) end },
__neg__ = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
if D.is_zero_raw(d) then return D.make_decimal_from_raw(d) end
return D.make_decimal_from_raw({ sign = -d.sign, limbs = d.limbs, limb_exp = d.limb_exp, special = d.special })
end },
__abs__ = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return D.make_decimal_from_raw({ sign = 1, limbs = d.limbs, limb_exp = d.limb_exp, special = d.special })
end },
__add__ = { __type = "builtin_method", __call = function(a) return bin_op(a[1], a[2], D.add_raw) end },
__radd__ = { __type = "builtin_method", __call = function(a) return rbin_op(a[1], a[2], D.add_raw) end },
__sub__ = { __type = "builtin_method", __call = function(a) return bin_op(a[1], a[2], D.sub_raw) end },
__rsub__ = { __type = "builtin_method", __call = function(a) return rbin_op(a[1], a[2], D.sub_raw) end },
__mul__ = { __type = "builtin_method", __call = function(a) return bin_op(a[1], a[2], D.mul_raw) end },
__rmul__ = { __type = "builtin_method", __call = function(a) return rbin_op(a[1], a[2], D.mul_raw) end },
__truediv__ = { __type = "builtin_method", __call = function(a) return bin_op(a[1], a[2], D.div_raw) end },
__rtruediv__ = { __type = "builtin_method", __call = function(a) return rbin_op(a[1], a[2], D.div_raw) end },
__floordiv__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false); if not rb then return B.NotImplemented end
local q, _ = D.divmod_raw(a[1].__dict, rb)
return D.make_decimal_from_raw(q)
end },
__rfloordiv__ = { __type = "builtin_method", __call = function(a)
local ra = D.coerce_to_raw(a[2], false); if not ra then return B.NotImplemented end
local q, _ = D.divmod_raw(ra, a[1].__dict)
return D.make_decimal_from_raw(q)
end },
__mod__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false); if not rb then return B.NotImplemented end
local _, r = D.divmod_raw(a[1].__dict, rb)
return D.make_decimal_from_raw(r)
end },
__rmod__ = { __type = "builtin_method", __call = function(a)
local ra = D.coerce_to_raw(a[2], false); if not ra then return B.NotImplemented end
local _, r = D.divmod_raw(ra, a[1].__dict)
return D.make_decimal_from_raw(r)
end },
__divmod__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false); if not rb then return B.NotImplemented end
local q, r = D.divmod_raw(a[1].__dict, rb)
return { __type = "tuple", elements = { D.make_decimal_from_raw(q), D.make_decimal_from_raw(r) } }
end },
__rdivmod__ = { __type = "builtin_method", __call = function(a)
local ra = D.coerce_to_raw(a[2], false); if not ra then return B.NotImplemented end
local q, r = D.divmod_raw(ra, a[1].__dict)
return { __type = "tuple", elements = { D.make_decimal_from_raw(q), D.make_decimal_from_raw(r) } }
end },
__pow__ = { __type = "builtin_method", __call = function(a) return bin_op(a[1], a[2], D.pow_raw) end },
__rpow__ = { __type = "builtin_method", __call = function(a) return rbin_op(a[1], a[2], D.pow_raw) end },
-- 比較運算
__eq__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], true)
if not rb then return false end
local c = D.cmp_raw(a[1].__dict, rb)
return c == 0
end },
__ne__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], true)
if not rb then return true end
local c = D.cmp_raw(a[1].__dict, rb)
return c ~= 0
end },
__lt__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], true)
if not rb then Runtime.raise("TypeError", "'<' not supported between Decimal and " .. type(a[2])) end
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then D.raise_dec("InvalidOperation", "comparison involving NaN") end
return c < 0
end },
__le__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], true)
if not rb then Runtime.raise("TypeError", "'<=' not supported") end
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then D.raise_dec("InvalidOperation", "comparison involving NaN") end
return c <= 0
end },
__gt__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], true)
if not rb then Runtime.raise("TypeError", "'>' not supported") end
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then D.raise_dec("InvalidOperation", "comparison involving NaN") end
return c > 0
end },
__ge__ = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], true)
if not rb then Runtime.raise("TypeError", "'>=' not supported") end
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then D.raise_dec("InvalidOperation", "comparison involving NaN") end
return c >= 0
end },
-- Decimal 專屬數學與工具方法
sqrt = { __type = "builtin_method", __call = function(a) return D.make_decimal_from_raw(D.sqrt_raw(a[1].__dict)) end },
ln = { __type = "builtin_method", __call = function(a) return D.make_decimal_from_raw(D.ln_raw(a[1].__dict)) end },
log10 = { __type = "builtin_method", __call = function(a) return D.make_decimal_from_raw(D.log10_raw(a[1].__dict)) end },
exp = { __type = "builtin_method", __call = function(a) return D.make_decimal_from_raw(D.exp_raw(a[1].__dict)) end },
logb = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
if D.is_zero_raw(d) then D.raise_dec("DivisionByZero", "logb of 0") end
if d.special == "Inf" then return D.make_decimal_from_raw(D.normalize_raw(1, {0}, 0, "Inf")) end
if d.special then return D.make_decimal_from_raw(D.normalize_raw(1, {0}, 0, "NaN")) end
local raw_digits, dec_exp = D.to_digits_and_exp(d)
local adj = (#raw_digits - 1) + dec_exp
return D.make_decimal_from_raw(D.coerce_to_raw(adj, true))
end },
adjusted = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
if d.special or D.is_zero_raw(d) then return 0 end
local raw_digits, dec_exp = D.to_digits_and_exp(d)
return (#raw_digits - 1) + dec_exp
end },
quantize = { __type = "builtin_method", __call = function(a, kw)
local self, exp_arg = a[1], a[2]
local rounding = (a[3] ~= nil and a[3]) or (kw and kw.rounding) or (D.current_context and D.current_context.__dict.rounding) or "ROUND_HALF_EVEN"
local exp_raw = D.coerce_to_raw(exp_arg, false)
if not exp_raw then Runtime.raise("TypeError", "quantize() first argument must be Decimal") end
local scale = 0
if D.is_decimal(exp_arg) and exp_arg.__dict._fixed_scale ~= nil then
scale = exp_arg.__dict._fixed_scale
elseif type(exp_arg) == "string" and exp_arg:find("%.", 1, false) then
local _, fp = exp_arg:match("^(.-)%.(.*)$")
scale = fp and #fp or 0
else
local _, de = D.to_digits_and_exp(exp_raw)
scale = -de
end
local d = self.__dict
local raw_digits, dec_exp = D.to_digits_and_exp(d)
local r_digits, r_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, -scale, rounding)
return D.make_decimal_from_raw(D.from_digits_and_exp(d.sign, r_digits, r_exp), scale)
end },
normalize = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return D.make_decimal_from_raw(D.normalize_raw(d.sign, d.limbs, d.limb_exp, d.special))
end },
to_integral_value = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
local rounding = (a[2] ~= nil and a[2]) or (kw and kw.rounding) or (D.current_context and D.current_context.__dict.rounding) or "ROUND_HALF_EVEN"
local d = self.__dict
if d.special then return D.make_decimal_from_raw(d) end
local raw_digits, dec_exp = D.to_digits_and_exp(d)
local r_digits, r_exp = D.round_to_exp(d.sign, raw_digits, dec_exp, 0, rounding)
return D.make_decimal_from_raw(D.from_digits_and_exp(d.sign, r_digits, r_exp))
end },
to_eng_string = { __type = "builtin_method", __call = function(a) return D.format_decimal(a[1], true, false) end },
as_tuple = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
local s_bit = (d.sign < 0) and 1 or 0
if d.special == "Inf" then
return require("Module:PythonScript/Evaluator").invoke(C.DecimalTuple, { s_bit, { __type = "tuple", elements = {0} }, "F" }, {})
elseif d.special == "NaN" then
return require("Module:PythonScript/Evaluator").invoke(C.DecimalTuple, { s_bit, { __type = "tuple", elements = {} }, "n" }, {})
elseif d.special == "sNaN" then
return require("Module:PythonScript/Evaluator").invoke(C.DecimalTuple, { s_bit, { __type = "tuple", elements = {} }, "N" }, {})
end
local raw_digits, dec_exp = D.to_digits_and_exp(d)
local d_elts = {}
for i = 1, #raw_digits do d_elts[i] = tonumber(raw_digits:sub(i, i)) end
return require("Module:PythonScript/Evaluator").invoke(C.DecimalTuple, { s_bit, { __type = "tuple", elements = d_elts }, dec_exp }, {})
end },
as_integer_ratio = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
if d.special == "Inf" then Runtime.raise("OverflowError", "cannot convert Infinity to integer ratio") end
if d.special then Runtime.raise("ValueError", "cannot convert NaN to integer ratio") end
local raw_digits, dec_exp = D.to_digits_and_exp(d)
if raw_digits == "0" then return { __type = "tuple", elements = { 0, 1 } } end
local sign_str = (d.sign < 0) and "-" or ""
if dec_exp >= 0 then
local num_int = B.int.__call({ sign_str .. raw_digits .. string.rep("0", dec_exp) })
return { __type = "tuple", elements = { num_int, 1 } }
end
local num = B.int.__call({ sign_str .. raw_digits })
local den = B.int.__call({ "1" .. string.rep("0", -dec_exp) })
local Evaluator = require("Module:PythonScript/Evaluator")
local x, y = B.abs({num}), den
while not (y == 0 or (type(y) == "table" and #y.limbs == 1 and y.limbs[1] == 0)) do
x, y = y, Evaluator.bigint_op(x, y, "%")
end
return { __type = "tuple", elements = { Evaluator.bigint_op(num, x, "//"), Evaluator.bigint_op(den, x, "//") } }
end },
copy_abs = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return D.make_decimal_from_raw({ sign = 1, limbs = d.limbs, limb_exp = d.limb_exp, special = d.special }, d._fixed_scale)
end },
copy_negate = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return D.make_decimal_from_raw({ sign = -d.sign, limbs = d.limbs, limb_exp = d.limb_exp, special = d.special }, d._fixed_scale)
end },
copy_sign = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
local rb = D.coerce_to_raw(a[2], true)
return D.make_decimal_from_raw({ sign = rb and rb.sign or 1, limbs = d.limbs, limb_exp = d.limb_exp, special = d.special }, d._fixed_scale)
end },
compare = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false)
if not rb then Runtime.raise("TypeError", "compare expects a Decimal") end
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then return D.make_decimal_from_raw(D.normalize_raw(1, {0}, 0, "NaN")) end
return D.make_decimal_from_raw(D.normalize_raw(c < 0 and -1 or 1, { c == 0 and 0 or 1 }, 0))
end },
max = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false)
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then return D.make_decimal_from_raw(D.normalize_raw(1, {0}, 0, "NaN")) end
return (c >= 0) and a[1] or D.make_decimal_from_raw(rb)
end },
min = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false)
local c = D.cmp_raw(a[1].__dict, rb)
if c == nil then return D.make_decimal_from_raw(D.normalize_raw(1, {0}, 0, "NaN")) end
return (c <= 0) and a[1] or D.make_decimal_from_raw(rb)
end },
fma = { __type = "builtin_method", __call = function(a)
local rb = D.coerce_to_raw(a[2], false)
local rc = D.coerce_to_raw(a[3], false)
return D.make_decimal_from_raw(D.add_raw(D.mul_raw(a[1].__dict, rb), rc))
end },
scaleb = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
local rb = D.coerce_to_raw(a[2], false)
local shift_n = tonumber(BigInt.to_string(rb.sign, rb.limbs)) or 0
local raw_digits, dec_exp = D.to_digits_and_exp(d)
return D.make_decimal_from_raw(D.from_digits_and_exp(d.sign, raw_digits, dec_exp + shift_n))
end },
radix = { __type = "builtin_method", __call = function() return D.make_decimal_from_raw(D.normalize_raw(1, {10}, 0)) end },
conjugate = { __type = "builtin_method", __call = function(a) return a[1] end },
canonical = { __type = "builtin_method", __call = function(a) return a[1] end },
is_canonical = { __type = "builtin_method", __call = function() return true end },
is_finite = { __type = "builtin_method", __call = function(a) return a[1].__dict.special == nil end },
is_infinite = { __type = "builtin_method", __call = function(a) return a[1].__dict.special == "Inf" end },
is_nan = { __type = "builtin_method", __call = function(a)
local sp = a[1].__dict.special
return sp == "NaN" or sp == "sNaN"
end },
is_qnan = { __type = "builtin_method", __call = function(a) return a[1].__dict.special == "NaN" end },
is_snan = { __type = "builtin_method", __call = function(a) return a[1].__dict.special == "sNaN" end },
is_normal = { __type = "builtin_method", __call = function(a)
return a[1].__dict.special == nil and not D.is_zero_raw(a[1].__dict)
end },
is_subnormal = { __type = "builtin_method", __call = function() return false end },
is_signed = { __type = "builtin_method", __call = function(a) return a[1].__dict.sign < 0 end },
is_zero = { __type = "builtin_method", __call = function(a) return D.is_zero_raw(a[1].__dict) end },
number_class = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
if d.special == "NaN" then return "NaN"
elseif d.special == "sNaN" then return "sNaN"
elseif d.special == "Inf" then return d.sign < 0 and "-Infinity" or "+Infinity"
elseif D.is_zero_raw(d) then return d.sign < 0 and "-Zero" or "+Zero"
else return d.sign < 0 and "-Normal" or "+Normal" end
end },
real = { __type = "property", fget = { __type = "builtin_method", __call = function(a) return a[1] end } },
imag = { __type = "property", fget = { __type = "builtin_method", __call = function() return D.make_decimal_from_raw(D.normalize_raw(1, {0}, 0)) end } }
}
dec_dict.to_integral = dec_dict.to_integral_value
dec_dict.to_integral_exact = dec_dict.to_integral_value
dec_dict.compare_total = dec_dict.compare
D.DecimalClass = PyClass.make_class("Decimal", {}, dec_dict)
D.DecimalClass.__dict["__module__"] = "decimal"
C.Decimal = D.DecimalClass
-- [E] Context 類別與上下文管理函式
local function make_status_dict()
local keys = { C.Clamped, C.InvalidOperation, C.DivisionByZero, C.Inexact, C.Rounded, C.Subnormal, C.Overflow, C.Underflow, C.FloatOperation }
local pairs_list = {}
for _, k in ipairs(keys) do table.insert(pairs_list, { key = k, value = 0 }) end
return { __type = "dict", pairs = pairs_list }
end
local ctx_dict = {
__init__ = { __type = "builtin_method", __call = function(a, kw)
local self = a[1]
kw = kw or {}
self.__dict.prec = (a[2] ~= nil and a[2]) or kw.prec or 28
self.__dict.rounding = (a[3] ~= nil and a[3]) or kw.rounding or "ROUND_HALF_EVEN"
self.__dict.Emin = (a[4] ~= nil and a[4]) or kw.Emin or -999999
self.__dict.Emax = (a[5] ~= nil and a[5]) or kw.Emax or 999999
self.__dict.capitals = (a[6] ~= nil and a[6]) or kw.capitals or 1
self.__dict.clamp = (a[7] ~= nil and a[7]) or kw.clamp or 0
self.__dict.flags = kw.flags or make_status_dict()
self.__dict.traps = kw.traps or make_status_dict()
return nil
end },
copy = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
local copy_inst = PyClass.make_instance(C.Context)
copy_inst.__dict.prec = d.prec
copy_inst.__dict.rounding = d.rounding
copy_inst.__dict.Emin = d.Emin
copy_inst.__dict.Emax = d.Emax
copy_inst.__dict.capitals = d.capitals
copy_inst.__dict.clamp = d.clamp
copy_inst.__dict.flags = make_status_dict()
copy_inst.__dict.traps = make_status_dict()
return copy_inst
end },
clear_flags = { __type = "builtin_method", __call = function(a)
for _, p in ipairs(a[1].__dict.flags.pairs or {}) do p.value = 0 end
return B.None
end },
clear_traps = { __type = "builtin_method", __call = function(a)
for _, p in ipairs(a[1].__dict.traps.pairs or {}) do p.value = 0 end
return B.None
end },
create_decimal = { __type = "builtin_method", __call = function(a)
return D.DecimalClass.__dict.__new__.__func.__call({ D.DecimalClass, a[2] or 0 })
end },
create_decimal_from_float = { __type = "builtin_method", __call = function(a)
return D.DecimalClass.__dict.from_float.__func.__call({ D.DecimalClass, a[2] })
end },
abs = { __type = "builtin_method", __call = function(a) return dec_dict.__abs__.__call({ a[2] }) end },
add = { __type = "builtin_method", __call = function(a) return dec_dict.__add__.__call({ a[2], a[3] }) end },
subtract = { __type = "builtin_method", __call = function(a) return dec_dict.__sub__.__call({ a[2], a[3] }) end },
multiply = { __type = "builtin_method", __call = function(a) return dec_dict.__mul__.__call({ a[2], a[3] }) end },
divide = { __type = "builtin_method", __call = function(a) return dec_dict.__truediv__.__call({ a[2], a[3] }) end },
divmod = { __type = "builtin_method", __call = function(a) return dec_dict.__divmod__.__call({ a[2], a[3] }) end },
power = { __type = "builtin_method", __call = function(a) return dec_dict.__pow__.__call({ a[2], a[3] }) end },
sqrt = { __type = "builtin_method", __call = function(a) return dec_dict.sqrt.__call({ a[2] }) end },
ln = { __type = "builtin_method", __call = function(a) return dec_dict.ln.__call({ a[2] }) end },
log10 = { __type = "builtin_method", __call = function(a) return dec_dict.log10.__call({ a[2] }) end },
exp = { __type = "builtin_method", __call = function(a) return dec_dict.exp.__call({ a[2] }) end },
__repr__ = { __type = "builtin_method", __call = function(a)
local d = a[1].__dict
return string.format("Context(prec=%d, rounding=%s, Emin=%d, Emax=%d, capitals=%d, clamp=%d)",
d.prec, tostring(d.rounding), d.Emin, d.Emax, d.capitals, d.clamp)
end }
}
ctx_dict.__str__ = ctx_dict.__repr__
C.Context = PyClass.make_class("Context", {}, ctx_dict)
C.Context.__dict["__module__"] = "decimal"
local function make_preset_ctx(prec, rounding)
local inst = PyClass.make_instance(C.Context)
ctx_dict.__init__.__call({ inst }, { prec = prec, rounding = rounding })
return inst
end
C.DefaultContext = make_preset_ctx(28, "ROUND_HALF_EVEN")
C.BasicContext = make_preset_ctx(9, "ROUND_HALF_UP")
C.ExtendedContext = make_preset_ctx(9, "ROUND_HALF_EVEN")
D.current_context = ctx_dict.copy.__call({ C.DefaultContext })
C.getcontext = { __type = "builtin_method", __name = "getcontext", __call = function()
return D.current_context
end }
C.setcontext = { __type = "builtin_method", __name = "setcontext", __call = function(a)
if not a or not a[1] then Runtime.raise("TypeError", "setcontext() requires a Context argument") end
D.current_context = a[1]
return B.None
end }
local local_ctx_mgr = PyClass.make_class("_ContextManager", {}, {
__enter__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
self.__dict._saved = D.current_context
D.current_context = self.__dict._work
return D.current_context
end },
__exit__ = { __type = "builtin_method", __call = function(a)
local self = a[1]
D.current_context = self.__dict._saved
return false
end }
})
C.localcontext = { __type = "builtin_method", __name = "localcontext", __call = function(a, kw)
local base_ctx = (a and not D.is_none(a[1]) and a[1]) or (kw and not D.is_none(kw.ctx) and kw.ctx) or D.current_context
local work_ctx = ctx_dict.copy.__call({ base_ctx })
if kw then
if kw.prec ~= nil then work_ctx.__dict.prec = kw.prec end
if kw.rounding ~= nil then work_ctx.__dict.rounding = kw.rounding end
end
local mgr = PyClass.make_instance(local_ctx_mgr)
mgr.__dict._work = work_ctx
return mgr
end }
C.IEEEContext = { __type = "builtin_method", __name = "IEEEContext", __call = function(a)
local bits = tonumber(a and a[1]) or 64
local prec_map = { [16] = 4, [32] = 7, [64] = 16, [128] = 34, [256] = 71, [512] = 146 }
local p = prec_map[bits] or math.max(1, math.floor(bits * 0.28))
return make_preset_ctx(p, "ROUND_HALF_EVEN")
end }
end
-- ==========================================
-- 7. 模組延遲載入匯出介面
-- ==========================================
function DecimalMod.get_module()
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, attr = args[1], args[2]
D.ensure_init()
local val = D.cache[attr]
if val ~= nil then
self.__dict[attr] = val
return val
end
Runtime.raise("AttributeError", "module 'decimal' has no attribute '" .. tostring(attr) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "decimal"
mod.__dict["__all__"] = {
__type = "list",
elements = {
"Decimal", "Context", "DecimalTuple",
"DefaultContext", "BasicContext", "ExtendedContext",
"DecimalException", "Clamped", "InvalidOperation", "ConversionSyntax",
"DivisionByZero", "DivisionImpossible", "DivisionUndefined",
"Inexact", "InvalidContext", "Rounded", "Subnormal", "Overflow", "Underflow", "FloatOperation",
"ROUND_DOWN", "ROUND_HALF_UP", "ROUND_HALF_EVEN", "ROUND_CEILING",
"ROUND_FLOOR", "ROUND_UP", "ROUND_HALF_DOWN", "ROUND_05UP",
"setcontext", "getcontext", "localcontext", "IEEEContext",
"MAX_PREC", "MAX_EMAX", "MIN_EMIN", "MIN_ETINY",
"HAVE_THREADS", "HAVE_CONTEXTVAR", "IEEE_CONTEXT_MAX_BITS"
}
}
return mod
end
return DecimalMod