浮点数比较
Zig中所有原始类型都支持 == 和 != 操作符。
但是对于浮点类型考虑精度问题直接使用==比较是不安全的。
正确的做法是使用近似相等
标准库std.math提供了专门用于浮点比较的函数
- 绝对误差比较
绝对误差表示两个数之间的实际差值大小
绝对误差=∣a−b∣
绝对误差比较使用 std.math.approxEqAbs
const std = @import("std");
const approxEqAbs = std.math.approxEqAbs;
const print = std.debug.print;
const a : f32 = 0.1 + 0.2;
const b :f32 = 0.3;
const epsilon: f32 = 1e-6;
if(std.math.approxEqAbs(f32, a, b, epsilon)) {
print("a ≈ b (abs)\n", .{});
}
- 相对误差比较
相对误差表示误差相对于真值或参考值的占比
相对误差=∣a−b∣/ max(∣a∣,∣b∣)
相对误差比较适用 std.math.approxEqRel
const std = @import("std");
const approxEqRel = std.math.approxEqRel;
const print = std.debug.print;
const x: f64 = 1e10 + 1.0;
const y: f64 = 1e10;
const rel_epsilon: f64 = 1e-12;
if (std.math.approxEqRel(f64, x, y, el_epsilon)) {
print("x ≈ y (rel)\n", .{});
}
绝对误差和相对误差选择
- 对于接近零,量级小的数值
a = 0.0001001
b = 0.0001000
- 绝对误差 =
0.0000001(非常小) - 相对误差 ≈
0.0000001 / 0.0001001 ≈ 0.001(即 0.1%)
用绝对误差判断更合理,因为数值很小,即使相对误差看起来不大,但是绝对误差可能已超过精度要求(超过精度要求我们认为是相等)
- 对于远离零、量级大的数值 编辑
a = 1000000.1
b = 1000000.0
- 绝对误差 =
0.1(看起来不小) - 相对误差 ≈
0.1 / 1000000.1 ≈ 0.0000001(即 0.00001%)
用相对误差判断更合理,虽然绝对误差0.1没超过精度,看起来不相等,但是对于百万级的数来说,这几乎可以忽略。
- 同时使用绝对和相对误差
同时使用绝对和相对误差是最健壮的方式
fn approxEq(a: f64, b: f64, abs_tol: f64, rel_tol: f64) bool {
const diff = @fabs(a - b);
return diff <= abs_tol or diff <= @max(@fabs(a), @fabs(b)) * rel_tol;
}
if (approxEq(0.1 + 0.2, 0.3, 1e-9, 1e-9)) {
// ✅ 安全通过
}
NaN和Inf
NaN (Not a Number) 和 Inf (Infinity)在IEEE754浮点标准定义中是合法的状态,它们不是错误。
在使用浮点数(f16、f32、f64、f128)进行数值计算时,必须考虑 NaN(Not a Number)和 Inf(Infinity)这两种特殊值的存在。
常见产生Inf的情况
| 表达式 | 结果 | 说明 |
|---|---|---|
1.0 / 0.0 | +Inf | 正数除以零(IEEE 754 定义) |
-1.0 / 0.0 | -Inf | 负数除以零 |
std.math.inf(f32) | +Inf | 显式获取正无穷 |
a * b(溢出) | ±Inf | 如 1e200 * 1e200(f64 中可能溢出) |
math.exp(1000) | +Inf | 指数函数结果过大 |
math.log(0.0) | -Inf | 对 0 取自然对数 |
常见产生NaN的情况
| 表达式 | 结果 | 数学解释 |
|---|---|---|
0.0 / 0.0 | NaN | 不定式(0/0 无定义) |
Inf - Inf | NaN | ∞ - ∞ 无定义 |
Inf / Inf | NaN | ∞ / ∞ 无定义 |
0.0 * Inf | NaN | 0 × ∞ 无定义 |
math.sqrt(-1.0) | NaN | 实数范围内负数无平方根 |
math.log(-1.0) | NaN | 负数无实对数 |
math.acos(2.0) | NaN | 反余弦定义域为 [-1, 1] |
NaN + 1.0 | NaN | NaN 具有“传染性” |
例如,写一个通用的打印Float信息的函数
fn printFloatInfo(comptime T: type, value: T) void {
_ = T; // 避免未使用警告
if (std.math.isNan(value)) {
std.debug.print("Value is NaN of type {}\n", .{@typeName(T)});
} else if (std.math.isInf(value)){
std.debug.print("Value is Inf of type {}\n", .{@typeName(T)});
} else {
std.debug.print("Value = {} (type {})\n", .{ value, @typeName(T) }); }
}
pub fn main() void {
printFloatInfo(f32, std.math.nan(f32)); // Value is NaN of type f32
printFloatInfo(f64, std.math.nan(f64)); // Value is NaN of type f64
}