feat(751-2): 원화 꼴 공용 한 벌 — common_util_money.ts · master_fmt.py 짝 · 거울 시험
- formatMoney(정수) · formatLineMoney(소수 1 · 끝 0 없음) · formatPrice(단가 소수 3 · 200 미만 유효숫자 넷) · formatNumber(수량 · 율 유효숫자 넷) - M01 Logic_Money · Logic_Edit 는 다시 내보내기만(부르는 쪽 그대로) - 파이썬 짝 master_fmt.py(절반 올림 · 은행가 반올림 어긋남 해소) · master_text 갈아 끼움은 받기 뒤 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""숫자 글 꼴 한 벌 — 금액 · 단가 · 수량 · 율 · 빈 값 · 음수. 셈 값은 그대로, 보이는 글만 만듦(반올림 = 절반 올림).
|
||||
|
||||
TS 짝: `common_util/common_util_money.ts` — 두 파일은 같은 글을 냄(거울 시험 `test_588_2_money_fmt.py`).
|
||||
fmt_money 원 단위 정수 (계 · 소계 · 합계 · 내역 금액)
|
||||
fmt_line_money 최대 소수 1 · 끝 0 뗌 (일위대가 줄 금액 · 산근 근거 줄)
|
||||
fmt_price 단가 — 200 원 미만 = 유효숫자 넷 · 그 밖 = 최대 소수 3 · 끝 0 뗌
|
||||
fmt 수량 · 율 · 품 — 소수 넷째 자리 · 유효숫자 넷 중 많은 쪽 · 끝 0 뗌
|
||||
값이 숫자가 아니면 빈 값 = 「」 · 그 밖 = str.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import ROUND_HALF_UP, Decimal, localcontext
|
||||
|
||||
|
||||
def _num(value) -> Decimal | None:
|
||||
"""숫자 → Decimal (불리언 · NaN · 무한대 · 숫자 아님 = None)."""
|
||||
if isinstance(value, bool):
|
||||
return None
|
||||
if isinstance(value, float):
|
||||
value = repr(value) # 짧은 글 → 자바스크립트 숫자와 같은 자리
|
||||
elif not isinstance(value, (Decimal, int)):
|
||||
return None
|
||||
out = Decimal(value)
|
||||
return out if out.is_finite() else None
|
||||
|
||||
|
||||
def _other(value) -> str:
|
||||
return "" if value is None else str(value)
|
||||
|
||||
|
||||
def _fixed(value: Decimal, places: int) -> str:
|
||||
"""소수 `places` 자리까지 절반 올림 · 끝 0 뗌 · 천 단위 쉼표."""
|
||||
with localcontext() as ctx:
|
||||
ctx.prec = 80
|
||||
cut = value.quantize(Decimal(1).scaleb(-min(places, 20)), rounding=ROUND_HALF_UP)
|
||||
sign, digits = ("-", -cut) if cut < 0 else ("", cut)
|
||||
whole, _, rest = f"{digits:f}".partition(".")
|
||||
rest = rest.rstrip("0")
|
||||
return sign + f"{int(whole):,}" + (f".{rest}" if rest else "")
|
||||
|
||||
|
||||
def fmt(value) -> str:
|
||||
d = _num(value)
|
||||
if d is None:
|
||||
return _other(value)
|
||||
return _fixed(d, 4 if not d else max(4, 3 - d.adjusted()))
|
||||
|
||||
|
||||
def fmt_money(value) -> str:
|
||||
d = _num(value)
|
||||
return _other(value) if d is None else _fixed(d, 0)
|
||||
|
||||
|
||||
def fmt_line_money(value) -> str:
|
||||
d = _num(value)
|
||||
return _other(value) if d is None else _fixed(d, 1)
|
||||
|
||||
|
||||
def fmt_price(value) -> str:
|
||||
d = _num(value)
|
||||
if d is None:
|
||||
return _other(value)
|
||||
return fmt(d) if abs(d) < 200 else _fixed(d, 3)
|
||||
@@ -0,0 +1,21 @@
|
||||
/* 원화 꼴 한 벌(`common_util_money.ts`) 거울 헬퍼 — 트랜스파일해 Node 로 돌림 ·
|
||||
* stdin = [값 글 | null ...] (숫자 글은 Number 로) · stdout = {함수: [글 ...]} */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const file = path.join(__dirname, "..", "..", "common_util", "common_util_money.ts");
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, require);
|
||||
|
||||
const cases = JSON.parse(fs.readFileSync(0, "utf8")).map((c) =>
|
||||
c !== null && c.num ? Number(c.num) : c === null ? null : c.text,
|
||||
);
|
||||
const out = {};
|
||||
for (const name of ["formatNumber", "formatMoney", "formatLineMoney", "formatPrice"]) {
|
||||
out[name] = cases.map((c) => box.exports[name](c));
|
||||
}
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,66 @@
|
||||
"""원화 꼴 한 벌 거울 시험 — `master_fmt.py`(파이썬) ↔ `common_util_money.ts`(TS · Node) 가 같은 글.
|
||||
|
||||
CLAUDE.md 5장 ① 짝. 값은 숫자 글(`num`) · 숫자 아닌 글(`text`) · 빈 값(None).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.join(HERE, "..", "master_data", "scripts"))
|
||||
|
||||
import master_fmt as mf # noqa: E402
|
||||
|
||||
NUMS = [
|
||||
"0", "1", "2.5", "-1234.5", "1.00005", "0.12345", "0.00167", "22.9", "1846.39",
|
||||
"124419.853", "275790", "69643.9394", "0.2525", "4", "199.99", "200", "1234567.891",
|
||||
"-0.00001", "0.05", "870.2455", "59020", "1414.9", "-22.96",
|
||||
] # fmt: skip
|
||||
CASES = [{"num": n} for n in NUMS] + [{"text": "가나다"}, {"text": ""}, None]
|
||||
PAIRS = {
|
||||
"formatNumber": mf.fmt,
|
||||
"formatMoney": mf.fmt_money,
|
||||
"formatLineMoney": mf.fmt_line_money,
|
||||
"formatPrice": mf.fmt_price,
|
||||
}
|
||||
|
||||
|
||||
def _ts() -> dict:
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_588_2_money_fmt.cjs")],
|
||||
input=json.dumps(CASES, ensure_ascii=False),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def _py_value(case):
|
||||
if case is None:
|
||||
return None
|
||||
return Decimal(case["num"]) if "num" in case else case["text"]
|
||||
|
||||
|
||||
def test_python_ts_same_text():
|
||||
ts_out = _ts()
|
||||
for name, py_fn in PAIRS.items():
|
||||
for case, ts_text in zip(CASES, ts_out[name]):
|
||||
assert py_fn(_py_value(case)) == ts_text, (name, case)
|
||||
|
||||
|
||||
def test_known_shapes():
|
||||
assert mf.fmt(Decimal("1.00005")) == "1.0001"
|
||||
assert mf.fmt(Decimal("0.00167")) == "0.00167"
|
||||
assert mf.fmt(Decimal("4")) == "4" # 율 — 「4 %」 (「4.0」 아님)
|
||||
assert mf.fmt_money(Decimal("124419.853")) == "124,420"
|
||||
assert mf.fmt_line_money(Decimal("69643.9394")) == "69,643.9"
|
||||
assert mf.fmt_line_money(Decimal("18064")) == "18,064" # 「.0」 꼬리 없음
|
||||
assert mf.fmt_price(Decimal("1846.39")) == "1,846.39" # 정수로 숨기지 않음
|
||||
assert mf.fmt_price(Decimal("22.9")) == "22.9"
|
||||
assert mf.fmt_money(Decimal("-0.4")) == "0"
|
||||
assert mf.fmt(None) == "" and mf.fmt("가") == "가"
|
||||
Reference in New Issue
Block a user