LSR 015 - LMMP 接口规范¶
基本信息¶
LSR 编号:015
标题:LMMP 接口规范
作者:Jericho Knox
状态:草案
类型:标准规范类
创建日期:06-02-2026
归属项目:LMMP
适用范围:LMMP 库所有标准与非标准 API、数据类型、内存模型、兼容约束等
替代文档:无
相关文档:LSR-003、LSR-004、LSR-018
摘要¶
本规范定义 LMMP 基础数值接口的数据域、工作精度、生命周期和可观测错误协议。 任意精度整数和有理数按资源允许的大小精确计算;浮点数始终使用有限工作精度, 不承诺以有限内存表示任意实数。幅值、认证区间和多项式是独立扩展,不是基础接口的先决条件。 本规范不限制内部存储、内核调用或分配策略。
1. 基础数值语义¶
1.1 类型与数域¶
类型 |
数域与精度 |
配置归属 |
|---|---|---|
|
任意精度有符号整数,精确;大小受可用资源限制 |
无上下文 |
|
精确有理数,分母非零;规范化为正分母及互素分子/分母 |
无上下文 |
|
固定 |
|
|
每个对象具有有限二进制有效位数、舍入模式和指数范围的近似实数 |
对象初始化时显式配置,无强制上下文 |
|
共享固定工作精度与指数范围的近似实数 |
|
这些是 C 数值表示,不是 Lamina 的独立基本数学静态类型。
语言层基本数学类型为 num,int、frac、real、complex 是值约束 [1]。
LMMP 基础接口不处理任意符号式、关系或一般复数;上层仅在取值和数域已确定时调用。
证明为整数但尚不能具体求值的符号式,不能直接转换成 lmpz_t 或机器整数。
整数加、减、乘不舍入;有理数运算保持精确。整数除法接口必须区分整除、商余数和有理数结果,
不得用同一未说明的 div 隐藏截断。基础 lmpz_divexact 要求除数非零且可整除;
lmpz_divrem 使用向零截断的商,满足 a = q*b + r、abs(r) < abs(b),
非零余数与被除数同号。lmpq_div 返回精确有理数。零除和非整除违反前提分别报告数域错误。
nint 溢出报告范围错误,不环绕、不饱和,也不自动变为另一种类型。
1.2 工作精度、舍入与范围¶
浮点 precision 是二进制有效位数,至少为 2;不是十进制显示位数、内存容量或无限位承诺。
lmpf 的配置在对象初始化时给定,nfloat 的配置在上下文初始化时给定,初始化后不可变。
改变配置须显式创建目标对象并转换,不能修改共享上下文使旧值获得新语义。
支持的舍入模式为最近值同距取偶、向零、向负无穷、向正无穷。 基础浮点加、减、乘、除按所选模式将数学运算结果舍入至目标对象的工作精度; 成功的舍入不等于保留原式全部信息。其他算法必须另行声明其精度契约, 不能借此承诺所有超越函数正确舍入或提供认证误差界。
非零浮点值表示为 m * 2^e,其中 1 <= abs(m) < 2,
e 必须处于初始化时给定的闭区间 [emin, emax],要求 emin <= 0 <= emax。
基础接口另支持精确零,不公开 NaN、无穷和次正规数。舍入后溢出或非零结果下溢报告范围错误,
不得静默返回无穷或零。实现须公布支持的精度和指数配置范围,超出范围的配置失败,
不得悄悄截成机器精度。浮点对象即使当前表示整数或有限小数,仍是近似表示。
混合精度 lmpf 运算先读取输入各自已有表示值,再以目标对象配置计算;
不先把输入降为目标精度。nfloat 运算要求所有对象使用同一上下文,
跨上下文须显式转换。提高目标工作精度不会恢复输入已丢失的信息。
语言层 approximate(value, precision=100) 的精度是十进制有效位数 [2],
适配层须按该目标选择足够的二进制工作精度,保留近似标记,不能把本接口的二进制位数直接当作十进制位数。
普通精确数学调用不能仅因内部选择 LMMP 浮点内核而隐式进入近似计算。
2. 公共 C 类型与错误通道¶
2.1 C11 声明¶
公共头可由 C11 直接包含;C++ 包装不得要求 C 调用者使用异常。 统一状态返回适用于所有可能失败的初始化、赋值、转换、查询和运算。 以下声明给出基础类型、状态和代表性接口;同类函数遵循相同契约:
#include <stddef.h>
#include <stdint.h>
typedef enum lmmp_status {
LMMP_OK = 0,
LMMP_INVALID_ARGUMENT,
LMMP_DOMAIN_ERROR,
LMMP_RANGE_ERROR,
LMMP_ALLOCATION_FAILURE,
LMMP_RESOURCE_EXHAUSTED,
LMMP_CANCELLED,
LMMP_INTERNAL_ERROR
} lmmp_status;
typedef enum lmmp_round {
LMMP_NEAREST_EVEN = 0,
LMMP_TOWARD_ZERO,
LMMP_DOWNWARD,
LMMP_UPWARD
} lmmp_round;
typedef struct lmpz_struct *lmpz_t;
typedef const struct lmpz_struct *lmpz_srcptr;
typedef struct lmpq_struct *lmpq_t;
typedef const struct lmpq_struct *lmpq_srcptr;
typedef struct lmpf_struct *lmpf_t;
typedef const struct lmpf_struct *lmpf_srcptr;
typedef struct nint_struct *nint_t;
typedef const struct nint_struct *nint_srcptr;
typedef struct nfloat_struct *nfloat_t;
typedef const struct nfloat_struct *nfloat_srcptr;
typedef struct nint_ctx_struct *nint_ctx_t;
typedef struct nfloat_ctx_struct *nfloat_ctx_t;
lmmp_status lmpz_init(lmpz_t *out);
lmmp_status lmpz_clear(lmpz_t *value);
lmmp_status lmpz_set_ui(lmpz_t rop, uint64_t value);
lmmp_status lmpz_add(lmpz_t rop, lmpz_srcptr a, lmpz_srcptr b);
lmmp_status lmpz_copy(lmpz_t rop, lmpz_srcptr value);
lmmp_status lmpz_move(lmpz_t *rop, lmpz_t *value);
lmmp_status lmpz_cmp(int *out, lmpz_srcptr a, lmpz_srcptr b);
lmmp_status lmpq_init(lmpq_t *out);
lmmp_status lmpq_clear(lmpq_t *value);
lmmp_status lmpq_div(lmpq_t rop, lmpq_srcptr a, lmpq_srcptr b);
lmmp_status lmpf_init(lmpf_t *out, size_t precision,
lmmp_round rounding, int64_t emin, int64_t emax);
lmmp_status lmpf_clear(lmpf_t *value);
lmmp_status lmpf_set_q(lmpf_t rop, lmpq_srcptr value);
lmmp_status lmpf_add(lmpf_t rop, lmpf_srcptr a, lmpf_srcptr b);
lmmp_status nint_ctx_init(nint_ctx_t *out, size_t bits);
lmmp_status nint_ctx_clear(nint_ctx_t *ctx);
lmmp_status nint_init(nint_t *out, nint_ctx_t ctx);
lmmp_status nint_clear(nint_t *value);
lmmp_status nint_add(nint_t rop, nint_srcptr a, nint_srcptr b,
nint_ctx_t ctx);
lmmp_status nfloat_ctx_init(nfloat_ctx_t *out, size_t precision,
lmmp_round rounding, int64_t emin, int64_t emax);
lmmp_status nfloat_ctx_clear(nfloat_ctx_t *ctx);
lmmp_status nfloat_init(nfloat_t *out, nfloat_ctx_t ctx);
lmmp_status nfloat_clear(nfloat_t *value);
lmmp_status nfloat_add(nfloat_t rop, nfloat_srcptr a, nfloat_srcptr b,
nfloat_ctx_t ctx);
不透明对象的布局不属于 ABI。srcptr 指向只读对象,而不是仅将句柄变量本身设为 const。
nint 的 bits 至少为 2,单位是位,不是 limb 数;不支持的宽度必须明确失败。
配置查询也采用 lmmp_status 返回、输出参数承载配置值的形式。
比较查询在成功时将输出设为 -1、0 或 1,只比较现有数值,不调用符号证明。
所有成功初始化的数值对象初始为零;lmpq 的规范分母为 1。
除 init 的空输出槽和 clear 的空句柄外,运算须使用有效、已初始化的对象;
可检查的空指针或空句柄返回 LMMP_INVALID_ARGUMENT。
2.2 状态与失败输出¶
LMMP_OK表示全部输出有效;非成功状态不得同时表示部分成功。非法参数或上下文不匹配返回
LMMP_INVALID_ARGUMENT;零除、非整除和定义域违反返回LMMP_DOMAIN_ERROR;机器整数、浮点指数或转换超范围返回LMMP_RANGE_ERROR。分配失败、资源限制、取消和内部错误分别返回对应状态,不得编译为仅 Debug 有效的检查, 也不得用
abort、C++ 异常、NaN 或默认结果代替公共错误协议。失败的赋值、转换和运算保持所有已初始化输出的旧值及配置;标量查询输出也保持原值。 多输出运算须全部提交或全部保持不变。
初始化要求输出句柄起始为
NULL;失败时仍为NULL,不产生待释放的半初始化对象。 清理成功后句柄为NULL;清理空句柄成功。有效对象的销毁不得因临时分配失败而遗失所有权。已有对象仍引用上下文时,
ctx_clear返回LMMP_INVALID_ARGUMENT并保持上下文有效。 调用者须先销毁对象;不得把这种可检查的生命周期冲突规定成正常使用下的未定义行为。状态不依赖进程全局或线程局部的“最后错误”变量。可附加诊断,但状态本身足以判断成功及类别。 悬空指针、伪造句柄和越界缓冲区仍违反 C 调用前提,不要求运行时识别任意内存破坏。
内部可保留不返回状态的 void 数值内核。公共入口必须在其调用前后按实际失败来源完成检查,
将失败转换为上述状态,保证输出契约;void 不意味着分配和运算不可能失败。
通过 Lamina FFI 调用时,适配层将状态映射为 LSR-018 [3] 的错误载荷:
参数/数域错误保持其类别,范围错误映射为 NumericalFailure,分配或资源失败映射为
ResourceLimit,取消映射为 Cancelled,内部错误映射为内部运行时故障。
任何非成功状态均不得吞掉后继续返回数学结果。
3. 生命周期、上下文与别名¶
3.1 生命周期与所有权¶
初始化取得一个对象的所有权,持有者负责清理。普通句柄赋值只复制 C 指针,
不复制数学对象,也不创建第二个可独立销毁的所有权。复制对象使用 copy;
move 只转移所有权,成功后目标取得对象、源为 NULL,失败时两者不变。
移动要求目标为空、源有效且两句柄槽不同,不执行隐式复制。
公共 API 不要求预先调用进程级全局初始化或维护强制内存池。
输入默认只读借用,仅在调用期间有效。输出对象由调用者所有,不能把结果指针指向临时存储。
运算允许 rop 与一个或两个输入为同一对象,语义等同先读取全部输入再写入输出;
不同输出不得相互别名。若某专用函数确实不支持别名,必须在其接口契约中明确限制,
不能把基础运算的别名行为留给底层实现猜测。
同一对象的并发写入需要调用方同步;互不关联的对象和上下文不得共享可观察的可变全局配置。
3.2 仅对需要的类型使用上下文¶
nint 和 nfloat 使用不透明上下文。配置初始化后只读,多实例可以共存,
同类多个实例本身不是错误。初始化绑定对象与上下文,运算时检查绑定是否匹配;
销毁对象从自身绑定获得清理所需信息,不强制再次传入冗余上下文。
上下文存活至所有关联对象销毁,不能跨上下文混用对象;显式转换除外。
无上下文的 lmpz、lmpq、lmpf 不添加无意义的 ctx 参数。
内存池、缓存、共享存储和复用临时空间都是内部可选优化,不改变所有权、别名或失败输出规则。
3.3 转换与精确/近似边界¶
精确整数、有理数间的转换须无损;有理数转整数要求分母为 1,不能自动截断。
向固定宽度整数转换须检查完整范围。浮点转整数要求当前表示值为整数且范围满足要求;
需要截断或其他舍入的调用者应使用命名明确的舍入接口。
精确值转浮点是显式数值转换,遵循目标配置并保留近似属性。
已有浮点值转换为精确有理数时只能表示当前存储数值,不代表恢复近似前数学对象;
语言适配层不得藉此消除来源值的近似身份。
对外部 double 的转换还须满足 LSR-003 [1] 的 ABI 范围和工作精度契约。
4. 调用示例¶
以下 C11 示例使用公共头提供的类型和声明;每一步检查状态,出错后只清理已取得的对象。
#include "lmmp/lmpz.h"
int main(void) {
lmpz_t a = NULL;
lmpz_t b = NULL;
lmpz_t c = NULL;
lmmp_status status = lmpz_init(&a);
if (status != LMMP_OK) goto done;
status = lmpz_init(&b);
if (status != LMMP_OK) goto done;
status = lmpz_init(&c);
if (status != LMMP_OK) goto done;
status = lmpz_set_ui(a, 1000);
if (status != LMMP_OK) goto done;
status = lmpz_set_ui(b, 2000);
if (status != LMMP_OK) goto done;
status = lmpz_add(c, a, b);
done:
lmpz_clear(&c);
lmpz_clear(&b);
lmpz_clear(&a);
return status == LMMP_OK ? 0 : 1;
}
5. 独立数值扩展¶
lmag_t 幅值、liar_t 认证区间及 lmpz_poly_t、lmpq_poly_t、
lmpf_poly_t、modn_poly_t 多项式分别属于独立扩展。
幅值扩展须声明非负性、上下界方向与范围;区间扩展须声明端点舍入、包含保证及空区间语义。
认证区间不等于 LSR-017 的符号实数集合区间,也不由普通近似值自动获得认证。
多项式扩展须声明系数域、零多项式、次数、除法条件及失败输出;
modn_poly_t 的系数域是模 n 剩余类,不是浮点数。
各扩展仅在实际需要精度、模数等配置时引入上下文,并沿用公共状态、生命周期和别名契约。
基础算术不依赖这些扩展,也不要求通用插件或注册框架。
6. 接口兼容性约束¶
标准接口的公开名称、C 原型、状态含义和数值契约属于兼容性范围,破坏性改变必须明确标识版本。 实现内部内核不因采用特定命名后缀而自动成为公共接口;非标准接口须明确其适用范围, 不得通过不检查错误的替代公开入口削弱基础契约。 所有公共头至少兼容 C11,可同时兼容 C++11;C++ 包含时提供 C 链接声明。