frontend/gda_expr 设计
设计目标
通用十进制算术(General Decimal Arithmetic)规范11 M. F. Cowlishaw, General Decimal Arithmetic Specification, version 1.70,以及随附的 decTest 套件。IEEE 754-2019 的十进制格式采用了相同的算术。 附带一个由 .decTest 文件组成的大型语料:每一行给出一个操作、其操作数、上下文、精确的预期结果,以及它必须引发的确切条件集合。本包将该语料转化为关于 decimal_gda 的可执行的有限结论:只有当 decimal_gda 产生相同的表示和相同的条件时,一行才算通过。本包是纯的(输入文本,输出汇总),因此可以在进程内测试、分片,并由一个精简的 CLI 驱动。
数学背景
十进制数据与上下文
一个有限 GDA 数是一个三元组 ,其中符号 ,整数系数 ,指数为 ,其值为 。不同的三元组可能具有相同的值:同一个值的所有表示构成其同值类(cohort),例如 和 分别对应 和 。规范规定了每个操作返回同值类中的哪个成员(理想指数),因此测试行的预期结果是一个表示,而不仅仅是一个值。特殊值包括 ,以及带整数载荷和符号的静默或信号 NaN。
上下文 给出精度、舍入模式和指数范围。操作 计算精确结果,在 下对其舍入,并引发十三种条件的一个子集:Inexact、Rounded、Lost_digits、Invalid_operation、Division_by_zero、Overflow、Underflow、Subnormal、Clamped、Conversion_syntax、Division_impossible、Division_undefined 和 Invalid_context。
行即操作
一行
id op a1 … an -> x c1 … cm
在指令上下文 下被降低为 numeric_expr 的表达式 ,并借助两个回调求值。字面量回调将每个 解码为一个 @decimal_gda.Decimal:
#后跟十六进制数字:IEEE 754 交换格式编码,按 与上下文相等的格式解码(、 或 );在其他任何上下文中该操作数无效;32#…、64#…、128#…:舍入到相应交换格式的十进制文本;- 否则为十进制文本(去掉开头的
+),以精度 解析,这使得不超过该位数的每个操作数都保持精确。
操作回调将规范化后的名称映射到一个 decimal_gda 函数(例如 add 映射到 @decimal_gda.add,squareroot 映射到 @decimal_gda.sqrt,comparetotal 映射到 @decimal_gda.compare_total),并以由 构造、禁用所有陷阱的 @decimal_gda.GdaContext 调用它。结果是四种类型之一的值(十进制数、整数、布尔值、文本),以及所引发条件的集合 。转换 tosci 和 toeng 读取原始操作数文本,因为从文本的转换本身就是被测对象。仅针对交换格式的操作(canonical,以及作用于 # 操作数并产生 # 结果的 apply、copy*)直接处理编码并返回文本。
通过规则
设 为实际结果, 为实际条件集合, 为所列条件的集合。定义 :当 包含 Division_impossible 或 Division_undefined 时向其加入 Invalid_operation(两者都通过无效操作信号报告)。该行通过当且仅当
其中标志集合的相等性在两个方向上检查:缺少或多出一个条件都会使该行失败。 取决于预期记号 和 的类型:
| 预期 | 实际 | |
|---|---|---|
? | 任意 | 真(只检查条件) |
#hex | 十进制数 | 在上下文格式中的交换格式编码等于 ,忽略字母大小写 |
32#…, 64#…, 128#… | 十进制数 | 在数值上等于 在该格式中的值(compare == 0);将 编码为该格式时引发的条件会加入 |
| 其他文本 | 十进制数 | 的打印结果与 完全相同,或 |
| 其他文本 | 整数 | 的十进制文本等于 |
| 其他文本 | 布尔 | 为 true/1 表示真,为 false/0 表示假,忽略大小写 |
| 文本 | 文本 | 字符串相等(对 #hex 忽略大小写) |
十进制情形在表示层面是精确的。IEEE 754 totalOrder22 IEEE 754-2019,第 5.10 节,totalOrder。Decimal::compare_total 为 decimal_gda 实现了它。 先比较符号,再比较类别,然后比较数值,最后比较指数,并按信号位和载荷对 NaN 排序。因此
所以 2.0 与 2.00 不匹配,-0 与 0 不匹配,NaN12 与 NaN 也不匹配。
设计决策
对不是测试的行给出处置类别而非判为失败
问题。 官方语料包含一些并不描述标量计算的行:用于无效或非标量编码的 # 占位符、来自旧版本的 ? 操作数、库未提供的操作,以及库不认识的舍入模式。将它们计为失败会掩盖真正的失败;静默丢弃它们则会夸大覆盖率。
选择。 每个被选中的行都会得到一个处置类别。Diagnostic 标记按构造即不可执行的行(# 或 ? 操作数、# 结果)。Unsupported 标记库无法运行的合法行(未知的操作、条件或舍入)。只有 Executable 行可以通过或失败,汇总报告每一类,因此诸如“所有可执行行都通过”这样的结论会附带它所排除的行数。严格性(有任何不支持项即失败)是调用者的策略:RunOptions 记录它,CLI 将其应用于退出码。
禁用陷阱,比较条件
.decTest 行列出的是条件,而不是陷阱。以空陷阱集运行每个操作,可使 decimal_gda 返回规定的默认结果(例如无效操作时返回静默 NaN)并报告所有引发的条件,这正是该行所陈述的内容。在两个方向上比较整个集合,可以同时捕获缺失的和多余的 Inexact、Rounded、Clamped 等条件。
表示精确的比较
GDA 规定了每个结果的指数,因此一个返回正确值但指数错误的库就是错误的。用 compare_total 或精确字符串进行比较,会将同值类、零的符号和 NaN 载荷都纳入测试。唯一的值层面比较用于 32#/64#/128# 结果,此时该行测试的是编码条件。
指令上下文仅在变化时解析一次
每一行都保存其所在行生效的指令记录。execute_documents 只在记录与前一行不同时才将其转换为 decimal_gda 上下文。转换是记录的纯函数,因此缓存永远不会改变结果;它消除了在同一上下文下包含数千行的文件中的重复工作。
确定性分片
问题。 语料很大,并在多个并行进程中运行,这些进程的结果相加必须等于串行运行的结果。
选择。 过滤之后,行按文档顺序编号为 , 个分片中的第 个分片取 。轮转分配会把含有慢操作(power、ln)的文件分散到各个分片,而不是让某一个分片承担整个慢文件。
正确性 / 不变式
划分。 对于 ,集合 两两不交且覆盖 ,因为每个 模 恰有一个余数。它们的大小是均衡的:
分片独立性。 一行的处置类别和结果只取决于该行本身(其记号和指令记录):解析为每一行确定记录,而上下文缓存记忆的是一个纯函数。因此,第 行的结果在包含它的每个分片中以及在串行运行中都相同,并且
因为 merge 将每个计数器相加,而各分片构成行的一个划分(total_cases 在每个分片中都是同一个 ,merge 会保留它)。
计数器恒等式。 对每个汇总,,,;这些等式成立,是因为每个结果都被 internal/conformance 恰好计入一个类别。
完全性。 解析将每一行恰好归入以下之一:跳过(空行或注释)、指令、测试行、诊断。执行永远不会因行内容而中止:解码和分派失败会成为带有消息 "evaluation failed" 的失败结果。
复杂度。 解析与文本长度呈线性关系。执行与行数呈线性关系,再加上十进制运算本身的开销。
被否决的替代方案
- 仅比较值。 更简单,但会接受错误的指数和零的符号,而规范和语料恰恰有意测试这些。
- 条件的子集比较(只要求引发所列的条件)。这会接受多余的
Inexact或Clamped,而这是一类常见的缺陷。 - 以临时约定的含义执行
#和?行。 这些行没有标量含义;凭空赋予一个含义会使通过计数失去意义。 - 连续分片。 将行列表切分为块同样是确定性的,但会把整个慢文件放进同一个分片。
边界
- 不负责文件系统访问、通配符展开或进程退出码:这些属于
cli/gda_expr_cli和tools/。 - 不使用陷阱:执行各行时禁用所有陷阱。
- 有效数字超过 位的操作数在解码时按就近舍入(偶数优先)处理。
- 不识别带引号的 decTest 字符串中的
''转义。 Legacy是共享结果模型的一部分,但当前的执行器从不分配它。- 该执行器只测试
decimal_gda;IEEE 十进制(decimal)在tools/中有自己的语料运行器。