# -*- 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)