LSR 015 - LMMP 接口规范

基本信息

  • LSR 编号:015

  • 标题:LMMP 接口规范

  • 作者:Jericho Knox

  • 状态:草案

  • 类型:标准规范类

  • 创建日期:06-02-2026

  • 归属项目:LMMP

  • 适用范围:LMMP 库所有标准与非标准 API、数据类型、内存模型、兼容约束等

  • 替代文档:无

  • 相关文档:LSR-003、LSR-004、LSR-018

摘要

本规范定义 LMMP 基础数值接口的数据域、工作精度、生命周期和可观测错误协议。 任意精度整数和有理数按资源允许的大小精确计算;浮点数始终使用有限工作精度, 不承诺以有限内存表示任意实数。幅值、认证区间和多项式是独立扩展,不是基础接口的先决条件。 本规范不限制内部存储、内核调用或分配策略。

1. 基础数值语义

1.1 类型与数域

类型

数域与精度

配置归属

lmpz_t

任意精度有符号整数,精确;大小受可用资源限制

无上下文

lmpq_t

精确有理数,分母非零;规范化为正分母及互素分子/分母

无上下文

nint_t

固定 bits 位有符号整数,范围 [-2^(bits-1), 2^(bits-1)-1]

nint_ctx_t

lmpf_t

每个对象具有有限二进制有效位数、舍入模式和指数范围的近似实数

对象初始化时显式配置,无强制上下文

nfloat_t

共享固定工作精度与指数范围的近似实数

nfloat_ctx_t

这些是 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 链接声明。

7. 引用