账本里同一币种的数字小数位数并不一致(10.5、10.50、10.5000),而
Decimal 保留每个数字自己的
exponent,原样输出得到参差不齐的列。解析器在构造 amount 或
compound_amount 时把结果数值传入
DisplayContext.update(),按币种累计小数位数的频率分布与整数部分最大位数;符号空间不依赖样本学习,_ContextBase.__init__
起无条件预留(第 4.1 节)。随后 build()
按调用方指定的精度策略(众数或最大值)与对齐方式(自然、小数点对齐、右对齐),根据累计状态为每个币种预计算一条
Python 格式串,交给 DisplayFormatter 逐个渲染。模块
docstring display_context.py:1-59
列出五个维度:PRECISION、ALIGNMENT、SIGN、COMMAS、RESERVED。它只影响输出文本,不参与记账计算:渲染是舍入,不是校验(第
6.4 节)。
| 名称 | 位置 | 作用 |
|---|---|---|
Distribution |
distribution.py:9-78 |
整数值直方图:update / update_from /
empty / min / max /
mode |
Precision |
display_context.py:81-85 |
MOST_COMMON = 1、MAXIMUM = 2 |
Align |
:88-93 |
NATURAL = 1、DOT = 2、RIGHT = 3 |
_ContextBase |
:96-176 |
单币种统计基类:has_sign、integer_max、update、update_from、__str__,三个抽象查询方法 |
_CurrencyContext |
:179-224 |
加挂
fractional_dist: Distribution,小数位数从统计里学 |
_FixedPrecisionContext |
:227-249 |
小数位数写死,integer_max 仍学习 |
DisplayContext |
:252-458 |
币种 →
上下文的字典;update、update_from、set_fixed_precision、quantize、build
与三个 _build_* |
DisplayContext.DEFAULT_UNINITIALIZED_PRECISION |
:412 |
值 8,仅 _build_dot 在没有任何小数位统计时使用 |
DisplayFormatter |
:461-495 |
持有预烤格式串及其 str.format
绑定方法;format(__call__)与
quantize |
DEFAULT_DISPLAY_CONTEXT /
DEFAULT_FORMATTER |
:499-500 |
模块导入时用空上下文构建的默认实例 |
def mode(self):
if not self.hist:
return None
max_value = 0
max_count = 0
for value, count in sorted(self.hist.items()):
if count >= max_count:
max_count = count
max_value = value
return max_value
distribution.py:64-78。hist 是
defaultdict(int)(:15),update
对样本计数加一(:25-31),update_from
按值把计数相加(:33-40)。mode
从小到大遍历并用 >=
比较,频率相同时后遇到的较大值覆盖前者,因此平局取较大的小数位数,与插入顺序无关(本机验证
{2:2, 4:2, 3:1} 正序、反序插入都得 4)。min /
max 每次调用都对全表 sorted()
再取首尾(:42-62)。distribution_test.py:21-32
锁定 mode=2, min=1, max=4;:35-53
锁定合并后众数从 2 变 3。类 docstring 说它用于 "compute a length that
will cover at least some decent fraction of the
samples"(:10-12)。2015-05-17 commit 9ea24584
把它从 utils.misc_utils 挪到
core.distribution,提交说明的理由是解开 core 对 utils
的依赖。
_ContextBase:符号位硬编码、整数位学习、update
是热点def __init__(self) -> None:
# Note: has_sign should always be assumed when formatting numbers; you
# never know if a new number may require a sign even though one was
# never witnessed. So we now hardcode to True. (Note to self: remove
# this later.)
self.has_sign = True
self.integer_max = 1
display_context.py:105-111。2025-05-29 commit
10b6f5ef 把 has_sign 初始值从
False 改成
True:余额、插值结果都可能产生输入文件里从未出现过的负数,不预留符号位会让后续渲染的列错位。update(:148-163)仍在见到负数时置
True(:157-159),已无实际效果;__str__(:113-146)输出的示例串因此总带前导
-(测试 display_context_test.py:395-414
的正则含 sign=1 与 "-0.00")。整数位数用
len(digits) + exponent
计算(:162),Decimal("0.00000125") 得 −5,被
max(..., 1) 兜住。:149-151 注释要求 "Please do
care for the performance of this routine" 并留下 "Consider
reimplementing this in C, after profiling";调用方
grammar.py:473,491 注明 number.as_tuple()
约增加
70ms,CHANGES:4355-4359(2014-11-09)记录初次引入时解析开销约
4%。:153-154 对 None 直接返回,但子类
_CurrencyContext.update(:201-205)在
super().update() 之后无条件调用
number.as_tuple(),None 在这里抛
AttributeError(本机验证);该守卫只对
_FixedPrecisionContext 有效(测试
:344-348)。三个 get_fractional* 在基类只
raise NotImplementedError(:169-176,2025-05-31
commit eaeaa645)。
_CurrencyContext:小数位数从分布里学def update(self, number: Decimal) -> None:
super().update(number)
# Update the precision.
num_tuple = number.as_tuple()
self.fractional_dist.update(-num_tuple.exponent)
def get_fractional(self, precision: Precision) -> int | None:
if self.fractional_dist.empty():
return None
if precision == Precision.MOST_COMMON:
return self.fractional_dist.mode()
elif precision == Precision.MAXIMUM:
return self.fractional_dist.max()
else:
raise ValueError("Unknown precision: {}".format(precision))
display_context.py:201-205,212-224。样本是
-exponent,即 Decimal
自身记录的小数位数:5000.0 记 1,764 记
0。空分布返回 None;__str__ 用的两个
get_fractional_digits_* 则返回
"_"(:193-199)。update_from(:207-210)把对方
cast 成 _CurrencyContext 再合并直方图,这个
cast 由 2025-08-03 commit
df4b77fe(#968)为类型检查加入,运行时不做检查。
_FixedPrecisionContext:精度写死,宽度照学display_context.py:227-249。构造时接收
fractional_digits(:238-240),三个查询方法都返回它,MOST_COMMON
与 MAXIMUM
无差别(:242-249);update /
update_from 继承基类,只更新
integer_max。2025-05-22 commit 9e148b76
引入,一周后 311f4ba3 抽出
_ContextBase,作者在提交说明里称这是 "a bit of ugly
concrete inheritance",因为都在一个文件里而接受。测试
display_context_test.py:312-414 五个用例逐项锁定。
__default__def __init__(self) -> None:
self.ccontexts: dict[str, _ContextBase] = collections.defaultdict(_CurrencyContext)
self.ccontexts["__default__"] = _CurrencyContext()
self.commas = False
display_context.py:261-264。defaultdict 让
update(:277-284)对新币种自动建上下文;__default__
是没传币种时的落点,也是 DisplayFormatter.format
对未见币种的回退(:485-490)。set_commas(:266-268)只存默认值,build(commas=None)
时取用(:352-353);解析器在
grammar.py:206-208 用 render_commas
选项灌入,注释说这是为了让逗号设定 "propagates everywhere it is used
automatically"。update_from(:286-293)按币种逐个合并,供
loader.py:550 在 include
多文件时把子文件的统计并入主文件(2021-05-15 commit
c46eaacd,作者 Xidorn
Quan)。set_fixed_precision(:295-296)直接用
_FixedPrecisionContext 替换该币种条目,之前学到的分布与
integer_max 一并丢弃(测试
:114-123;解析路径上的影响见 6.3)。
quantize:临时抬高 prec 的来龙去脉qdigit = Decimal(1).scaleb(-num_fractional_digits)
with decimal.localcontext() as ctx:
# Allow precision for numbers as large as 1 billion in addition to
# the required number of fractional digits.
#
# TODO(blais, 2020-11-25): Review this to assess performance impact,
# and whether we could fold this outside a calling loop.
# NOTE(blais, 2025-12-21): Not sure what the context was, but this
# prevents computation on very large numbers. Consider removing this
# or rewriting.
ctx.prec = num_fractional_digits + 12
return number.quantize(qdigit)
display_context.py:319-331。精度为
None(从未见过该币种)时原数返回(:316-318)。Decimal.quantize
在结果系数长度超过上下文 prec 时抛
InvalidOperation:issue #584
的病态输入让某币种推断出极大的小数位数,随后量化一个普通整数就超出默认的
28 位(本机验证
Decimal("100").quantize(Decimal(1).scaleb(-27))
在默认上下文报错)。2020-11-25 commit 4c1e87bb 引入
localcontext 并设
prec = 小数位 + 9,提交说明写明 "allowing for numbers as
large as 1B" 并怀疑会拖慢渲染;2025-12-21 commit c9132bba
改成 + 12 并加上
NOTE,作者自述已不记得原始上下文。ctx.prec
限的是量化结果的总有效数字数,不是直接检查输入整数部分位数:当前
+ 12 设置最多容纳量化结果 12
位整数部分,999999999999.99(12 位整数)可量化;但
999999999999.999 量化到两位小数时因四舍五入进位到 13
位整数,同样抛
InvalidOperation(本机验证)——超限不只发生在原始位数已经过长的输入上。模块不设置舍入模式,format
与 quantize 都沿用调用时的 decimal 上下文:Python
默认上下文是
ROUND_HALF_EVEN(quantize(2.345) → 2.34、quantize(2.355) → 2.36),仓库非测试代码没有任何
ROUND_
设置,但调用方可以改变这一行为(本机验证:ROUND_UP 上下文下
Decimal("2.341") 用 .2f 格式化与
quantize 都得 2.35,ROUND_DOWN
上下文下都得
2.34)。assert isinstance(number, Decimal)(:313)在
python -O 下失效。
build 与三种对齐build(:333-364)按 alignment
分派到三个私有方法,各自返回 {币种: 格式串},再封装成
DisplayFormatter(self, precision, fmtstrings)(:364)。每次调用都重新计算,没有缓存(TODO:553-554
列为待办)。
NATURAL(:366-377)不看宽度,reserved
参数名为 unused_reserved:
fmtfmt = (
"{{:{comma}f}}" if num_fractional_digits is None else "{{:{comma}.{frac}f}}"
)
:371-373。有统计时是 {:.2f},无统计时是
{:f}。f 后缀由 2017-07-23 commit
1fa59c71(#179)加上:Decimal 走不带类型码的
format 会在指数大时输出科学计数法(本机验证
"{:}".format(Decimal("1E+10")) 得 1E+10,加
f 得 10000000000)。
RIGHT(:379-410)先对所有币种算宽度上界:符号
1 位、integer_max、逗号位
int(integer_max / 3)、小数点 1
位(小数位非零时)、小数位数,取最大再加
reserved(:381-395);然后每个币种用同一
width 生成 {:11.2f}
这类格式串(:398-409)。无统计的币种退回
{:{width}{comma}},没有
f(:402-405),科学计数法在这条路径上仍可能出现(本机验证
"{:2}".format(Decimal("1E+10")) 得
1E+10)。逗号位估算 int(n/3) 是上界:3
位整数不需要逗号却算 1。
DOT(:414-458)分别求最大符号位、最大整数位(含逗号估算)、是否有小数点、最大小数位(:416-433);无任何小数位统计时把最大小数位设为
8(:412,435-436),所以未初始化的上下文在 DOT 下补到 8
位小数,与 NATURAL / RIGHT 的原样输出不同(测试 :225-230
对照
:129-134,170-175)。符号位既计入总宽(:421-422
在任一币种 has_sign 时置
max_sign = 1,:438 把它并入
max_width),也在格式串里用空格符号标志体现(:442,451),使正数占住那一位。每个币种的格式串宽度为
max_width - len_padding,右侧再拼 len_padding
个空格(:446-457),整数币种在有小数点的列里额外多补一位(:449-450),使小数点纵向对齐。测试
:238-254 的五币种样例本机复现为 {: 10.4f} 加 4
空格(USD)、{: 5.0f} 加 9
空格(CAD)、{: 14.8f}(RBFF)。
DisplayFormatterself.fmtfuncs = {currency: fmtstr.format for currency, fmtstr in fmtstrings.items()}
def format(self, number: Decimal, currency: Currency = "__default__") -> str:
try:
func = self.fmtfuncs[currency]
except KeyError:
func = self.fmtfuncs["__default__"]
return func(number)
display_context.py:480,485-490。构造时把每条格式串的
.format
绑定方法取出(:480),format
只剩一次字典查找加一次调用;__call__ = format(:495)。quantize
转发给 dcontext.quantize 并带上自身构建时的
precision(:492-493,2015-09-12 commit
e2720590)。这个 quantize
接口在仓库非测试代码里没有调用点;插值走的是 interpolate.py
里由容差推出的 quantum。
display_context.py:498-500:DEFAULT_DISPLAY_CONTEXT = DisplayContext()、DEFAULT_FORMATTER = DEFAULT_DISPLAY_CONTEXT.build()。空上下文构建的格式串只有
{'__default__': '{:f}'},效果是保留 Decimal
自身的小数位、不加逗号;amount.py:63、position.py:155,209、inventory.py:110
用它作默认参数,printer.py:116 在没传 dcontext
时用 DEFAULT_DISPLAY_CONTEXT 再 build。
def _dcupdate(self, number, currency):
"""Update the display context."""
if isinstance(number, Decimal) and currency and currency is not MISSING:
self.display_context_update(number, currency)
grammar.py:175-178。Builder.__init__ 建一个
DisplayContext 并缓存 update
绑定方法(:172-173);仓库里只有
amount(:461-475)和
compound_amount(:477-497)调用
_dcupdate,且要求结果是
Decimal、币种非空且不是 MISSING。语法层
grammar.y 中 posting 单位与 @ 价格走
incomplete_amount(:420-421,670-675),balance
与 price 指令走
amount(:607-619,722),成本走
compound_amount(:652-668),全部汇入统计;balance
的 ~ 容差数字只作为第二个值传给
Builder.balance,不进统计(grammar.y:621-634)。元数据
key_value_value(:465-475)与
custom 指令
custom_value(:758-782)都允许裸
number_expr,这类数字不经
amount/compound_amount,不进入统计。number_expr
的四则运算在 C 里用 PyNumber_* 对 Decimal
直接求值(:316-349);在默认 decimal
上下文(prec=28)下,作为有币种 amount 使用的
10/3 会产生 27 位小数样本,具体位数取决于调用时的 decimal
上下文精度(本机验证:localcontext().prec=6 时只得 5
位小数)。finalize(grammar.py:180-215)在解析结束时灌入两个选项:render_commas(:208)与
display_precision(:211-213,取示例数字的
-exponent 调
set_fixed_precision);get_options
把上下文挂到
options["dcontext"](:225-234)。options.py:449-482
的 display_precision 文档说统计推断 "led to a lot of
confusion",因此开放显式指定(:459-466),并指出宽度仍从输入学习(:468-469);但
set_fixed_precision(:295-296)用全新
_FixedPrecisionContext
替换整个币种条目,integer_max 随之回到
1(:110-111),而 finalize 是在
parser.parse()
跑完整个文件之后才被调用(parser.py:226-227),此时该币种已经学到的宽度被丢弃,之后也不再有数字流入重新学习,options.py:468-469
的说法在这条解析路径上不成立。值经
options_validate_tolerance_map(:73-89)转成
(currency, D(str))。options.py:258-265 声明
dcontext 选项,默认值是一个新
DisplayContext();:607-613 声明
render_commas,默认 False。
self.dformat = self.dcontext.build(precision=display_context.Precision.MOST_COMMON)
self.dformat_max = self.dcontext.build(precision=display_context.Precision.MAXIMUM)
printer.py:117-118。两份 formatter 的分工:posting 的
units 与 cost 经 position.to_string(posting, self.dformat)
用众数(:288;position.py:155-175,69-104),Balance
金额与 ~ 容差用众数(:319,327),只有 posting
的 @ 价格(:299)与 price
指令(:396)用
dformat_max。渲染即舍入:{:.2f} 把三位小数的
posting 静默舍到两位,DisplayFormatter.format
不做任何精度丢失检查(display_context.py:485-490);Balance
的隐式容差由金额 exponent
推出(ops/balance.py:34-41),打印成众数位数后重新解析,容差随之改变。TODO:4843-4845
的 issue #107 "Review all codes that renders units, costs and prices for
precision" 仍在待办,TODO:567-568 另有 "issue a warning if
numbers are rendered through it that lose some
precision",TODO:503-507 记录 formatter 只有单一精度、而
to_string 同时打印 units / cost / price
的矛盾。cost_to_str 对 CostSpec 的数字调用
dformat.format(number)
不传币种(position.py:96,99,102),落到
__default__ 上下文。doctor.py:542-548 的
display_context 命令直接 str(dcontext)
打印每币种一行统计;:422 的
render_mini_balances(被 linked 与
region 调用,:349,402)用
build(alignment=Align.DOT, reserved=2),是非测试代码里唯一同时使用
DOT 对齐与 reserved
的调用点;core/realization_test.py:702-703 的
test_dump_balances 也用同一组合覆盖
dump_balances。
| 决策 | 理由 | 证据 |
|---|---|---|
| 精度按币种从输入统计,默认取众数 | 同一币种绝大多数数字位数一致;少数高精度数字不应拉长整列 | display_context.py:16-20;CHANGES:4324-4331 |
| 另提供 MAXIMUM 供价格使用 | 价格与汇率需要保留更多位 | :20;printer.py:118,299,396;TODO:490-491 |
统计与渲染分离:DisplayContext 是
builder,DisplayFormatter 是产物 |
格式串一次烤好、到处传递,渲染只剩字典查找 | :364,480;CHANGES:4187-4191(2014-12-26) |
has_sign 硬编码 True |
新数字可能带符号;未预留会错位 | :106-110;commit 10b6f5ef |
格式串带 f |
禁止科学计数法出现在账本输出 | :372;commit 1fa59c71(#179) |
quantize 用 localcontext 抬
prec |
极端精度下量化普通整数会超出默认 28 位 | :321-331;commit
4c1e87bb、c9132bba |
提供固定精度上下文与 display_precision 选项 |
统计推断让用户困惑,需要显式覆盖 | options.py:459-466;commit 9e148b76 |
render_commas 织进 DisplayContext |
让逗号设定随上下文自动传播到所有渲染点 | grammar.py:206-208;CHANGES:2381-2393(#106) |
| 空上下文作为默认 formatter | 没有统计时原样输出,不引入舍入 | :498-500 |
update_from 跨文件合并 |
include 的子文件各有自己的 Builder 与统计 | loader.py:550;commit c46eaacd |
| 现象 | 后果 | 证据 |
|---|---|---|
| 众数平局取较大值 | 四个位数各异的样本 → 8 位;test_natural_no_clear_mode
正是这种情况 |
distribution.py:74-75;display_context_test.py:136-140 |
| 舍入模式随调用时的 decimal 上下文 | 模块不设置舍入模式;Python 默认上下文是
ROUND_HALF_EVEN(2.345 → 2.34),调用方改上下文后
format/quantize 结果一起变 |
仓库无 ROUND_
设置;:321-331,371-376;本机验证 |
| 渲染即舍入,不校验 | 高于众数精度的 posting 被静默截短;只有 @ 价格与
price 指令用最大精度 |
printer.py:288,299,319,327,396;:485-490 |
quantize 的 prec 限的是结果总位数 |
当前设置容纳 12 位整数部分的量化结果;舍入进位到第 13 位同样抛
InvalidOperation,不限于原始位数超长的输入 |
:319-331;本机验证 |
integer_max 只取单个数字的最大整数位 |
不是多个数累加后余额的宽度,RIGHT/DOT
可能预留不足;reserved 只是调用方手动补偿 |
TODO:539-544;:161-163,379-395,414-438 |
DisplayFormatter.format 是快照,.quantize
是实时 |
build() 之后再 update 同一个
DisplayContext,format
仍用旧格式串,quantize 却读到新精度 |
:362-364,474-480,485-493 |
_CurrencyContext.update(None) 抛
AttributeError |
基类的 None
守卫对学习型上下文无效;_dcupdate 已先过滤非
Decimal |
:153-154,201-205;grammar.py:177 |
quantize 对未见币种有副作用 |
defaultdict 为该币种插入空上下文,之后
build 的格式串里多一个键 |
:262,314;本机验证 |
学习型上下文 update_from 固定型抛
AttributeError |
子文件对某币种设了 display_precision 而主文件没设时
loader.py:550 合并失败;反向合并正常 |
:209-210;本机验证 |
RIGHT 未初始化格式串无 f |
Decimal("1E+10") 渲染成 1E+10;#179 只修了
NATURAL |
:402-405;本机验证 |
| DOT 未初始化补 8 位小数 | 与 NATURAL / RIGHT 的原样输出不一致 | :412,435-436;测试 :225-230 |
| 正指数样本产生负小数位 | Decimal("1E+3") 入统计后格式串为
{:.-3f},渲染抛 ValueError;词法规则
lexer.l:282-283 不接受指数写法,解析路径不会触发 |
:205,372;本机验证 |
| 语法层除法结果直接入统计 | 默认 decimal 上下文下 10/3 贡献 27
位小数样本,MAXIMUM 被拉到
27;位数随调用时上下文精度变化(第 6.3 节) |
grammar.y:333-337;本机验证 |
reserved 在 NATURAL 下被忽略 |
只有 RIGHT / DOT 加宽 | :366,395,438;测试 :159-164 |
逗号位估算 int(n/3) |
上界而非精确值,n 为 3 的倍数时多一位 | :388,426 |
set_fixed_precision 丢弃已学分布 |
之后 MOST_COMMON / MAXIMUM 无区别 |
:295-296;测试 :87-95,114-123 |
build 无缓存 |
每次构建都遍历全部币种 | :333-364;TODO:553-554 |
| 【文档漂移】docstring 说无负号时 "we save the space" | has_sign 恒为 True,空间总是预留 |
:48-49,100-101,110 |
【文档漂移】_FixedPrecisionContext docstring "Sign ...
still learned" |
符号位不再学习 | :228 |
【文档漂移】DisplayContext docstring "construct a
DisplayContext from a series of numbers" |
构建的是 DisplayFormatter |
:253 |
| 【文档漂移】测试注释 "most common is 0"、"has_sign=False" | EUR 样本位数 4 / 0 / 8 平局取 8;has_sign 恒真 |
display_context_test.py:111,352 |
【文档漂移】TODO 仍列 "Implement reserved number of
digits"、"Add display_precision input file option" |
前者自 2016-03-12 commit 47fd0b71 起可用,后者
2025-05-22 已实现 |
TODO:556,570-571 |
display_context_test.py 共 28 个测试、6 个类;辅助方法
assertFormatNumbers(:27-48)先
update 再 build 再逐个
format,noinit=True 跳过
update。distribution_test.py 2 个测试。
| 测试 | 行号 | 锁定的行为 |
|---|---|---|
test_dump |
52-57 | str(dcontext) 含 sign= |
test_set_fixed_precision |
59-123 | 固定精度下 MOST_COMMON 与 MAXIMUM
同结果;quantize(99.999) → 100.00;替换后类型变为
_FixedPrecisionContext |
test_natural_* |
129-164 | 未初始化原样输出;平局取 8 位;众数 2 位、最大 4
位;逗号;reserved 无效 |
test_right_* |
170-219 | 有无负数宽度相同;整数列宽;逗号加宽;小数补零与舍入
0.0002 → 0.00 |
test_dot_* |
225-298 | 未初始化补 8 位;多币种小数点对齐与右侧补空格;整数币种在有小数列里多补一位 |
test_quantize_basic |
302-309 | 量化位数随后续 update 从 2 变 4 |
TestFixedContext |
313-414 | has_sign 恒真;integer_max
仍学习;update(None) 安全;update_from
不改精度;__str__ 格式 |
test_distribution / test_update_from |
distribution_test.py:22-32,36-53 |
mode / min / max / empty;合并后众数变化 |
未覆盖:get_fractional 收到非法 Precision
时的三种分支——非空 _CurrencyContext 抛
ValueError、空 _CurrencyContext
在检查枚举值之前就已返回
None、_FixedPrecisionContext 完全忽略
precision
返回固定值(:212-224,248-249);build 收到非法
Align 的
ValueError(:361);quantize
对未知币种返回原数(:316-318)与 12
位整数上限;_CurrencyContext.update(None);学习型上下文合并固定型的方向(_CurrencyContext.update_from(_FixedPrecisionContext),即第
8 节中会抛 AttributeError 的那条;反方向已由
display_context_test.py:350-374,388-393
覆盖);DisplayContext.update_from
本身(只测过币种上下文层);DisplayFormatter.quantize、__call__;set_commas
经 build(commas=None) 的默认传播;RIGHT
未初始化路径的科学计数法;__default__ 回退。
本模块之外的集成测试:parser/options_test.py:166-234
覆盖 display_precision
的解析、生效与非法值;core/realization_test.py:685-715 覆盖
DOT 对齐加
reserved=2;parser/printer_test.py:711-730
是禁止科学计数法的回归测试;scripts/doctor_test.py:146-165
覆盖 display-context 命令。
| 日期 | 提交 / 记录 | 变化 |
|---|---|---|
| 2014-11-09 | CHANGES:4355-4359 |
解析器开始记录数字精度分布,开销约 4% |
| 2014-11-11 | 7ac0cddb;CHANGES:4324-4335 |
创建模块;Amount.str(int) 改为
to_string(DisplayContext);逗号默认关闭,新增
render_commas |
| 2014-11-15 / 16 | 1237033b、e76f840d、9fa0f875 |
builder 模式;reserved 暂以
NotImplementedError 禁用 |
| 2014-11-22 | 6c32a8b3 |
NumFormatter 更名 DisplayFormatter,变量
numfmt → dformat |
| 2014-12-28 | 2afb4f9c |
DisplayContext.quantize |
| 2015-05-17 | 9ea24584 |
Distribution 从 utils.misc_utils 移入
core.distribution |
| 2015-07-12 / 20 | CHANGES:3504-3510,3487-3489 |
web / report 与 bean-query 改用 DisplayContext 渲染 |
| 2015-09-12 | e2720590 |
DisplayFormatter.quantize |
| 2016-03-12 | d29e74a3(#106)、47fd0b71 |
render_commas 真正生效;reserved
恢复可用 |
| 2017-07-23 | 1fa59c71(#179) |
NATURAL 格式串加 f |
| 2017-12-09 | 97cabe0a;CHANGES:1236 |
price 指令按显示精度渲染(改动在
prices/price.py) |
| 2020-11-25 | 4c1e87bb(#584) |
quantize 用
localcontext,prec = 小数位 + 9 |
| 2021-05-15 | c46eaacd |
三层 update_from,供 include 合并 |
| 2025-05-22 | 9e148b76 |
_FixedPrecisionContext 与
display_precision 选项 |
| 2025-05-29 | 311f4ba3、10b6f5ef |
抽出 _ContextBase;has_sign 硬编码
True |
| 2025-08-03 | df4b77fe(#968) |
ccontexts 注解为
dict[str, _ContextBase],update_from 加
cast |
| 2025-12-21 | c9132bba |
prec 改为 + 12,加 NOTE |
distribution.py(:75);decimal.localcontext(:321);Currency = str(data.py:30)仅供类型注解。不依赖
amount / number,因此
amount.py:21 可以在模块级导入
DEFAULT_FORMATTER 而不成环。grammar.py:172-178(Builder
持有并更新)、:206-213(选项灌入)、:232(挂到
options);options.py:264 声明 dcontext
选项;loader.py:532-560 合并子文件。amount.py:63-72、position.py:69-104,155-175,209、inventory.py:110-120
的 to_string 接收
dformat;printer.py:116-118,288,299,319,327,396;doctor.py:422,542-548。interpolate.py 用容差推 quantum;Balance
隐式容差在 ops/balance.py:34-41 由 exponent
推出。两者与显示精度各自独立,只在"打印后再解析"时通过本模块的舍入间接耦合。beancount/core/display_context.py:1-59 模块
docstring(16-20 精度、24-46 对齐示例、48-49 SIGN、51-52 COMMAS、54-57
RESERVED);75 导入 distribution;77-78
TYPE_CHECKING;81-85 Precision;88-93
Align;96-176 _ContextBase(105-111
__init__、113-146 __str__、148-163
update、165-167 update_from、169-176
抽象方法);179-224
_CurrencyContext(189-191、193-199、201-205、207-210、212-224);227-249
_FixedPrecisionContext;252-458
DisplayContext(261-264、266-268、270-275、277-284、286-293、295-296、298-331
quantize、333-364 build、366-377
_build_natural、379-410
_build_right、412、414-458
_build_dot);461-495
DisplayFormatter(474-480、485-490、492-493、495);498-500
默认实例。
beancount/core/distribution.py:9-12 类
docstring;14-15 __init__;17-23 empty;25-31
update;33-40 update_from;42-51
min;53-62 max;64-78 mode。
测试:display_context_test.py
14-21、24-48、52-57、59-123、129-164、170-219、225-298、302-309、313-414;distribution_test.py
13-18、22-32、36-53。
其它:beancount/parser/grammar.py:21,172-178,180-215,225-234,461-475,477-497;beancount/parser/grammar.y:316-349,420-421,465-475,607-634,652-675,722,758-782;beancount/parser/lexer.l:282-283;beancount/parser/parser.py:224-227;beancount/parser/options.py:73-89,258-265,449-482,607-613;beancount/loader.py:532-560;beancount/parser/printer.py:116-118,288,299,319,327,396;beancount/core/position.py:69-104,155-175,209;beancount/core/inventory.py:52,110-120;beancount/core/amount.py:21,63;beancount/core/data.py:30;beancount/ops/balance.py:29-45;beancount/scripts/doctor.py:28,256,349,391,402,405,422,542-548;CHANGES:83-85,1236,1290,2368-2371,2381-2393,3487-3489,3504-3510,4187-4191,4324-4335,4355-4359;TODO:486-499,503-507,527-534,539-544,553-554,556,567-571,4843-4845。
集成测试:parser/options_test.py:166-234;core/realization_test.py:685-715,702-703;parser/printer_test.py:711-730;scripts/doctor_test.py:146-165。
commit:7ac0cddb(2014-11-11)、1237033b(2014-11-15)、e76f840d(2014-11-16)、9fa0f875(2014-11-16)、6c32a8b3(2014-11-22)、2afb4f9c(2014-12-28)、9ea24584(2015-05-17)、e2720590(2015-09-12)、d29e74a3(2016-03-12)、47fd0b71(2016-03-12)、1fa59c71(2017-07-23)、97cabe0a(2017-12-09)、4c1e87bb(2020-11-25)、c46eaacd(2021-05-15)、9e148b76(2025-05-22)、311f4ba3(2025-05-29)、10b6f5ef(2025-05-29)、eaeaa645(2025-05-31)、df4b77fe(2025-08-03)、c9132bba(2025-12-21)。