Yuzhe's Blog

yuzhes

RedScript v2.5.0:双精度运算、N 阶贝塞尔曲线与标准库重构

RedScript v2.5.0:双精度运算、N 阶贝塞尔曲线与标准库重构

今天是 RedScript 开发历程中的一个里程碑:v2.5.0 发布,带来了全面重新设计的数值类型系统、在 Minecraft 标量板(scoreboard)引擎中运行的 IEEE 754 双精度浮点运算、N 阶贝塞尔曲线,以及大规模标准库扩展——测试用例数量从 1277 增至 1485。以下是这次发布的完整内容。

类型系统重设计

第一个变化是一次早该做的命名澄清。旧的 float 类型——它从来就不是真正的浮点数——现在更名为 fixed(定点数)。它以标量板(scoreboard)整数的形式存储值,精度缩放比为 ×10000,本质上是一个 32 位定点数类型。旧名称一直存在误导性。

与此同时,全新的 double 类型进入语言:标准的 IEEE 754 64 位浮点数,底层不依赖标量板,而是通过 rs:d 命名空间下的 NBT 存储来支撑。这是一种本质上不同的数据——它存在于 NBT 中,而非标量板,对其进行操作需要专用函数调用,而非标量板算术。

两者之间的类型转换现在只能显式进行。凡是发生转换的地方,都需要写明 x as doublex as fixed。编译器不再对数值类型进行隐式强转。新增了一条 lint 诊断 [FloatArithmetic],每当旧的 float 名称出现在算术表达式中时就会触发——这是为现有代码库提供的迁移辅助工具。

Double 运算:Minecraft 黑魔法

这一部分需要最多的创造性工程设计。Minecraft 的标量板系统基于 32 位整数运算,没有任何原生浮点指令。然而实体坐标,以及 NBT 存储中 double 类型的值,在 JVM 内部均以 IEEE 754 64 位双精度浮点数的形式存储。关键在于间接利用这一事实。

加法:实体坐标技巧(Entity Position Trick)

Minecraft 实体将其 Pos 数组存储为双精度浮点数。核心洞见在于:loot spawn 可以在任意坐标处生成实体,没有范围限制(不同于 tp 命令,后者会将坐标夹紧到 ±30,000,000)。我们生成一个标记实体,并利用相对传送来执行加法:

# a + b:
$tp <marker> $(x) 0 0
# → 将 marker 传送到 x = a(通过函数宏注入)

$execute at <marker> run tp <marker> ~$(dx) 0 0
# → 将 marker 移动 +b(相对位移,最终 x = a + b)

data get entity <marker> Pos[0]
# → 读取结果:a + b,类型为 double

相对 tp 指令(~)在 JVM 层面以双精度完成加法运算。我们将游戏本身的坐标计算当作一个浮点算术逻辑单元(ALU)来使用。

乘法:函数宏(Function Macro)缩放注入

execute store 要求其 scale 参数必须是编译期字面量——不能传入变量。解决方法是使用 MC 函数宏在运行时动态注入缩放值:

execute store result storage rs:d __scale double 0.0001 \
  run scoreboard players get $f __ns
# → __scale = f × 0.0001(一个 double,存储 f/10000)

$execute store result storage rs:d out double $(scale) \
  run data get storage rs:d input 1
# → out = input × $(scale) = d × (f/10000)

通过预先将 __scale 计算为 double,再用宏将其拼入 execute store 命令,我们实现了定点数 × 双精度的乘法,无需任何编译期常量折叠。

除法:展示实体(Display Entity)SVD

Minecraft 展示实体(Display Entity)拥有一个变换矩阵,游戏通过奇异值分解(SVD)对其进行分解,以提取平移、旋转和缩放分量。通过构造矩阵,使其缩放分量等于 input / divisor,游戏引擎便会替我们在硬件层面完成除法——实际上是将浮点除法卸载到了渲染器的矩阵分解代码中执行。

编译器内置(Compiler Intrinsic)

所有这些底层机制在语言层面都是透明的。当 ab 均为 double 变量时,编写 a + b 现在会在 MIR 降级阶段自动转换为 double_add(a, b) 调用。编译器根据操作数类型自动选择正确的内置实现。用户代码无需任何语法变更。

新增标准库模块

parabola.mcrs

用于投射物模拟的弹道轨迹数学:

quaternion.mcrs

展示实体(Display Entity)的旋转在内部使用四元数(quaternion)。该模块涵盖了驱动它们所需的完整四元数代数:

这里修复了一个值得关注的 bug:SLERP 归一化步骤调用 mulfix 时使用了错误的缩放比例,导致模长约为 31622 而非期望的 10000。问题根源在于归一化步骤中漏掉了一次开方——中间值被直接使用,未经开方处理,使缩放比例被放大了 √10000 = 100 倍。

bezier_quartic + bezier_n

通过 De Casteljau 算法实现的 N 阶贝塞尔曲线(Bezier curve)。bezier_n 在控制点数组上原地操作(有破坏性,但速度快);bezier_n_safe 会先复制一份,保留原始控制点以供重复求值。两个函数均接受定点数参数 t ∈ [0, 10000]

bigint_mul / bigint_sq

使用标量板(scoreboard)数组实现的任意精度整数运算。bigint_mul 对两个多肢整数进行乘法;bigint_sq 是一个优化的平方运算,利用对称性将所需乘法次数减少约一半。

高精度(High-Precision)数学

ln_hp:牛顿-拉弗森迭代修正

现有的 ln_5term 函数使用 5 项级数近似计算 ln(x),可提供约 6 位有效数字精度。ln_hp 在其基础上包裹了一次牛顿-拉弗森修正步骤:

y₀ = ln_5term(x)          # 初始估计值
correction = (x - exp(y₀)) × 10000 / exp(y₀)
y₁ = y₀ + correction      # 精化后的结果

对于接近初始估计值的输入,修正步骤在单次迭代内即可收敛到机器精度,将精度提升至 8–9 位有效数字——足以满足大多数物理模拟的需求。

signal.mcrs 中的统计分布

四个新的采样函数加入信号处理模块:

这些函数使得粒子系统、程序化生成和概率性 AI 行为得以完全在数据包(datapack)内运行——无需任何外部工具。

数据指标

指标更新前更新后变化
测试用例数12771485+208
标准库函数约 40 个新增
数值类型floatintfixeddoubleint+1

本次发布的所有提交均已 GPG 签名。更新日志位于仓库根目录的 CHANGELOG.md


v2.5.0 是 RedScript 数值模型自语言创建以来最重要的一次变革。双精度运算解锁了此前对数据包而言遥不可及的一类物理和渲染问题。标准库的扩展使这些基础构件唾手可得,无需在每个项目中重复造轮子。