Zig 浮点数比较

40 阅读3分钟

浮点数比较

Zig中所有原始类型都支持 == 和 != 操作符。

但是对于浮点类型考虑精度问题直接使用==比较是不安全的。

正确的做法是使用近似相等

标准库std.math提供了专门用于浮点比较的函数

  1. 绝对误差比较

绝对误差表示两个数之间的实际差值大小

绝对误差=∣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", .{});
}
  1. 相对误差比较

相对误差表示误差相对于真值或参考值的占比

相对误差=∣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", .{});
}

绝对误差和相对误差选择

  1. 对于接近零,量级小的数值
a = 0.0001001
b = 0.0001000
  • 绝对误差 = 0.0000001(非常小)
  • 相对误差 ≈ 0.0000001 / 0.0001001 ≈ 0.001(即 0.1%)

用绝对误差判断更合理,因为数值很小,即使相对误差看起来不大,但是绝对误差可能已超过精度要求(超过精度要求我们认为是相等)

  1. 对于远离零、量级大的数值 编辑
a = 1000000.1
b = 1000000.0
  • 绝对误差 = 0.1(看起来不小)
  • 相对误差 ≈ 0.1 / 1000000.1 ≈ 0.0000001(即 0.00001%)

用相对误差判断更合理,虽然绝对误差0.1没超过精度,看起来不相等,但是对于百万级的数来说,这几乎可以忽略。

  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浮点标准定义中是合法的状态,它们不是错误。

在使用浮点数(f16f32f64f128)进行数值计算时,必须考虑 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.0NaN不定式(0/0 无定义)
Inf - InfNaN∞ - ∞ 无定义
Inf / InfNaN∞ / ∞ 无定义
0.0 * InfNaN0 × ∞ 无定义
math.sqrt(-1.0)NaN实数范围内负数无平方根
math.log(-1.0)NaN负数无实对数
math.acos(2.0)NaN反余弦定义域为 [-1, 1]
NaN + 1.0NaNNaN 具有“传染性”

例如,写一个通用的打印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 
}