深色模式
精度、误差与数值检查
一个数打印了很多小数位,并不代表测量准确。精度(precision)描述表达或重复结果的细致程度;准确度(accuracy)描述接近真实值的程度。软件也可能非常稳定地算出错误结果:单位错、参考系错、假设错,都不会自动触发异常。
几百万米旁边的一毫米
浮点数(floating-point number)用有限位数保存有效数字和指数。JavaScript 的普通 number 使用双精度格式;GPU 数据常使用单精度格式。单精度在更大的数附近,相邻可表示数之间的间隔也更大。
| 名词 / 英文 | 含义 | 例子 |
|---|---|---|
| 舍入误差 / rounding error | 数值被替换成可表示的邻近值 | Float32 无法保存某些厘米差异 |
| 量化误差 / quantization error | 连续范围映射到有限整数编码 | 温度存为 8 位通道 |
| 截断误差 / truncation error | 用有限步骤近似连续过程 | Euler 代替连续轨迹 |
| 模型误差 / model error | 数学模型与现实的差异 | 球替代椭球 |
| 绝对误差 / absolute error | abs(estimate−reference) | m、°C 等原单位 |
| 相对误差 / relative error | 绝对误差除以参考值绝对值 | 无量纲;参考值为零时另定尺度 |
在 6,378,137 m 附近,Float32 相邻数的间隔约为 0.5 m。若先把两个相近的 ECEF 数各自转为 Float32,再相减,0.1 m 的差异可能已经消失:
ts
const origin = 6_378_137
const point = origin + 0.1
const lost = Math.fround(point) - Math.fround(origin) // 0
const preserved = Math.fround(point - origin) // 约0.1Math.fround 模拟单精度舍入。先在足够精度下减去附近原点,再上传局部坐标,是保留小尺度细节的一种思路。Cesium 还使用高低位编码(high/low encoding)、相对相机位置等策略;自定义 Shader 应接入相应管线,不能假定直接上传 ECEF Float32 总能满足精度。
手算:一条颜色通道能装多精细
将 10–30°C 映射到 0–255 的整数,量化间隔是 20/255 ≈ 0.07843°C。若使用四舍五入,且没有越界截断,解码后的最大量化误差约为半个间隔,即 0.03922°C:
ts
function encodeTemperature(celsius: number) {
const ratio = Math.max(0, Math.min(1, (celsius - 10) / 20))
return Math.round(ratio * 255)
}
const decodeTemperature = (byte: number) => 10 + byte / 255 * 20
decodeTemperature(encodeTemperature(20)) // 约20.03922°C对于均匀量化(uniform quantization),误差界限可写为:
超出 10–30°C 的输入会被夹住,这属于范围截断,不受“半个间隔”的误差界限保证。缺测值若占用某个编码,还会改变有效编码范围。纹理过滤、色带映射与显示器最终颜色又是其他步骤;颜色看起来一致不代表原值完全相同。
找出误差在哪一层
先用小而能预测答案的数据建立参考值:零风不应移动;恒定东风应有可计算的位移;线性温度场的双线性插值应吻合解析值。再改变一个因素,例如纹理格式、网格分辨率或时间步长。
如果将积分步长减半后误差明显变小,问题可能与时间离散有关;如果无论怎样减步长都偏移约一格,应检查索引映射与半纹素偏移。结果不能只与另一份同样算法互相比较,还应使用独立的手算或解析例子。
视觉高度夸张(vertical exaggeration)、模拟时间倍率和拟真噪声都是显示或模型选择。它们需要独立参数与标签,避免把屏幕效果当成测量结果。
自检:选择正确的比较尺度
为什么不建议用 computed === expected 比较一般浮点计算结果?参考值为零时,怎样检查误差?
答案与理由
多步运算可能因舍入产生微小差异,应按需求设置绝对和相对容差,例如 abs(a−b) <= absTol + relTol × abs(b)。参考值接近零时,绝对容差承担主要作用;它必须带有明确单位与业务尺度。也不要把很宽的容差当成修复错误公式的方法。