mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:21:58 +00:00
144 lines
5.6 KiB
Python
144 lines
5.6 KiB
Python
"""分析结论里的数:精确的有理数进、规范的十进制字符串出(细案 01 §1「数用十进制」)。
|
||
|
||
结论要能逐字节复算(证据面板、导出、可验证报告的复算和封存的逐字段比,评测作答前后各算一次逐字比),所以:
|
||
|
||
- **算**:快照里的数一律换成 Fraction(浮点按最短写法 repr,数字文本按字面,DECIMAL 落成的文本不经浮点),加减乘除全程
|
||
精确——各段之和逐位等于总变化,Σ结构效应 + Σ自身效应逐位等于比率的变化。只有要开方、取对数、查分布的统计量是浮点。
|
||
- **写**:canon 把数写成规范的十进制字符串:不带指数、不带正号、去掉多余的 0、没有「-0」。能精确写成有限小数的(快照里的
|
||
数和它们的和差,0.25 这样的商)照写;除不尽的和浮点按 SIG 位有效数字四舍五入(ROUND_HALF_UP)。浮点只留 12 位有效
|
||
数字:不同机器的数学库在最后一两位上可能不一样,留 12 位同一份输入在哪台机器上复算都是同一串字。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from decimal import Decimal, InvalidOperation
|
||
from fractions import Fraction
|
||
from typing import Any
|
||
|
||
#: 除不尽的数、浮点保留几位有效数字
|
||
SIG = 12
|
||
#: 能精确写成有限小数、但有效数字超过这么多位的,也按 SIG 位写(只会出现在反复除以 2 这类极端情形)
|
||
_EXACT_MAX_DIGITS = 30
|
||
|
||
|
||
def to_fraction(value: Any) -> Fraction | None:
|
||
"""一个数换成精确的 Fraction:整数、Decimal、Fraction 原样;浮点按最短写法(repr);数字文本按字面。布尔、非数、无穷、
|
||
认不出的为 None。"""
|
||
if isinstance(value, bool) or value is None:
|
||
return None
|
||
if isinstance(value, Fraction):
|
||
return value
|
||
if isinstance(value, int):
|
||
return Fraction(value)
|
||
if isinstance(value, float):
|
||
if not math.isfinite(value):
|
||
return None
|
||
return Fraction(Decimal(repr(value)))
|
||
if isinstance(value, Decimal):
|
||
return Fraction(value) if value.is_finite() else None
|
||
if isinstance(value, str):
|
||
try:
|
||
d = Decimal(value.strip())
|
||
except (InvalidOperation, ValueError):
|
||
return None
|
||
return Fraction(d) if d.is_finite() else None
|
||
return None
|
||
|
||
|
||
def _terminating(q: Fraction) -> int | None:
|
||
"""q 能精确写成有限小数时要几位小数;不能为 None。"""
|
||
d = q.denominator
|
||
twos = fives = 0
|
||
while d % 2 == 0:
|
||
d //= 2
|
||
twos += 1
|
||
while d % 5 == 0:
|
||
d //= 5
|
||
fives += 1
|
||
return max(twos, fives) if d == 1 else None
|
||
|
||
|
||
def _plain(digits: int, exp10: int) -> str:
|
||
"""digits × 10^exp10(digits ≥ 0)写成不带指数、去尾零的十进制。"""
|
||
text = str(digits)
|
||
if exp10 >= 0:
|
||
return text + "0" * exp10 if digits else "0"
|
||
places = -exp10
|
||
text = text.rjust(places + 1, "0")
|
||
head, tail = text[:-places], text[-places:].rstrip("0")
|
||
return f"{head}.{tail}" if tail else head
|
||
|
||
|
||
def _decade(a: Fraction) -> int:
|
||
"""a > 0 的十进制量级:10^e ≤ a < 10^(e+1) 的 e。"""
|
||
try:
|
||
e = math.floor(math.log10(a.numerator) - math.log10(a.denominator))
|
||
except (ValueError, OverflowError):
|
||
e = len(str(a.numerator)) - len(str(a.denominator))
|
||
while Fraction(10) ** e > a:
|
||
e -= 1
|
||
while Fraction(10) ** (e + 1) <= a:
|
||
e += 1
|
||
return e
|
||
|
||
|
||
def round_sig(q: Fraction, sig: int = SIG) -> Fraction:
|
||
"""按 sig 位有效数字四舍五入(ROUND_HALF_UP)。"""
|
||
if q == 0:
|
||
return Fraction(0)
|
||
sign = -1 if q < 0 else 1
|
||
a = abs(q)
|
||
e = _decade(a)
|
||
scale = Fraction(10) ** (sig - 1 - e)
|
||
m = math.floor(a * scale + Fraction(1, 2))
|
||
return sign * Fraction(m) / scale
|
||
|
||
|
||
def round_places(q: Fraction, places: int) -> Fraction:
|
||
"""按 places 位小数四舍五入(ROUND_HALF_UP,负数按绝对值舍入,同 Decimal 的 ROUND_HALF_UP)。"""
|
||
sign = -1 if q < 0 else 1
|
||
scale = Fraction(10) ** places
|
||
return sign * Fraction(math.floor(abs(q) * scale + Fraction(1, 2))) / scale
|
||
|
||
|
||
def _write(q: Fraction) -> str:
|
||
"""有限小数的 q 写出来(调用方保证 q 能精确写成有限小数)。"""
|
||
places = _terminating(q) or 0
|
||
sign = "-" if q < 0 else ""
|
||
digits = abs(q) * (10 ** places)
|
||
assert digits.denominator == 1
|
||
return sign + _plain(int(digits), -places)
|
||
|
||
|
||
def canon(value: Any) -> str | None:
|
||
"""规范的十进制字符串(见文件头)。None、非数、无穷为 None。"""
|
||
if isinstance(value, float) and math.isfinite(value):
|
||
q = round_sig(to_fraction(value) or Fraction(0))
|
||
else:
|
||
q = to_fraction(value)
|
||
if q is None:
|
||
return None
|
||
places = _terminating(q)
|
||
if places is None or len(str(int(abs(q) * 10 ** places)).rstrip("0")) > _EXACT_MAX_DIGITS:
|
||
q = round_sig(q)
|
||
if q == 0:
|
||
return "0"
|
||
return _write(q)
|
||
|
||
|
||
def fixed(q: Fraction, places: int, *, strip: bool = True, group: bool = False) -> str:
|
||
"""按 places 位小数四舍五入写出来;strip 去掉尾零,group 加千分位。没有正号;舍成 0 的写「0」(不写「-0」)。"""
|
||
r = round_places(q, places)
|
||
sign = "-" if r < 0 else ""
|
||
scaled = abs(r) * (10 ** places)
|
||
whole, frac = divmod(int(scaled), 10 ** places)
|
||
head = f"{whole:,}" if group else str(whole)
|
||
tail = str(frac).rjust(places, "0") if places > 0 else ""
|
||
if strip:
|
||
tail = tail.rstrip("0")
|
||
text = f"{head}.{tail}" if tail else head
|
||
return sign + text
|
||
|
||
|
||
__all__ = ["SIG", "canon", "fixed", "round_places", "round_sig", "to_fraction"]
|