Chapter 50Random And Math

随机数和数学

概述

有了上一章中的压缩管道,我们现在关注为这些工作流提供支持的数字引擎:确定性伪随机数生成器、行为良好的数学辅助函数以及平衡速度和安全性的哈希原语。Zig 0.15.2保持这些组件模块化——std.Random构建可重现序列,std.math提供谨慎的容差和常量,标准库将哈希分为非加密和加密家族,以便您可以为每个工作负载选择正确的工具。math.zigwyhash.zigsha2.zig

学习目标

  • 播种、推进和重现 std.Random 生成器,同时采样常见分布。Xoshiro256.zig
  • 应用 std.math 工具——常量、钳位、公差和几何辅助函数——以保持数值代码稳定。hypot.zig
  • 区分像Wyhash这样的快速哈希器与像SHA-256这样的加密摘要,并将两者负责任地连接到文件处理作业中。

随机数基础

Zig将伪随机生成器作为一等值公开:您为引擎播种,向其请求整数、浮点数或索引,您的代码拥有状态转换。这种透明度为您提供了对模糊器、仿真和确定性测试的控制。Random.zig

具有可重现序列的确定性生成器

std.Random.DefaultPrng 包装 Xoshiro256++,当您调用 init(seed) 时通过SplitMix64为自己播种。从那里您获得一个 Random 外观,公开高级辅助函数——范围、洗牌、浮点数——同时保持底层状态私有。

Zig
const std = @import("std");

pub fn main() !void {
    var stdout_buffer: [4096]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    const seed: u64 = 0x0006_7B20; // 424,224 in decimal
    var prng = std.Random.DefaultPrng.init(seed);
    var rand = prng.random();

    const dice_roll = rand.intRangeAtMost(u8, 1, 6);
    const coin = if (rand.boolean()) "heads" else "tails";
    var ladder = [_]u8{ 0, 1, 2, 3, 4, 5 };
    rand.shuffle(u8, ladder[0..]);

    const unit_float = rand.float(f64);

    var reproducible = [_]u32{ undefined, undefined, undefined };
    var check_prng = std.Random.DefaultPrng.init(seed);
    var check_rand = check_prng.random();
    for (&reproducible) |*slot| {
        slot.* = check_rand.int(u32);
    }

    try stdout.print("seed=0x{X:0>8}\n", .{seed});
    try stdout.print("d6 roll -> {d}\n", .{dice_roll});
    try stdout.print("coin flip -> {s}\n", .{coin});
    try stdout.print("shuffled ladder -> {any}\n", .{ladder});
    try stdout.print("unit float -> {d:.6}\n", .{unit_float});
    try stdout.print("first three u32 -> {any}\n", .{reproducible});

    try stdout.flush();
}
Run
Shell
$ zig run prng_sequences.zig
Output
Shell
seed=0x00067B20
d6 roll -> 5
coin flip -> tails
shuffled ladder -> { 0, 4, 3, 2, 5, 1 }
unit float -> 0.742435
first three u32 -> { 2135551917, 3874178402, 2563214192 }

uintLessThan 的公平性保证依赖于生成器的均匀输出;当常量时间行为比完美分布更重要时,回退到 uintLessThanBiased

使用分布和采样启发式

除了均匀抽取,Random.floatNormRandom.floatExp 公开基于Ziggurat的正态和指数样本——非常适合合成工作负载或噪声注入。ziggurat.zig 加权选择来自 weightedIndex,而Xoshiro引擎上的 .jump() 确定性地跳跃2^128步以跨线程分区流而不重叠。29 对于加密用途,交换到 std.crypto.randomstd.Random.DefaultCsprng 以继承基于ChaCha的熵,而不是快速但可预测的PRNG。tlcsprng.zig

实用数学工具

std.math 命名空间将基本常量与测量工具相结合:钳位、近似相等和几何辅助函数在所有CPU目标上都共享一致的语义。

数值卫生工具包

结合少量辅助函数——sqrtclamp、近似相等和黄金比例常量——保持报告代码可读和可移植。sqrt.zig

Zig
const std = @import("std");

pub fn main() !void {
    var stdout_buffer: [4096]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    const m = std.math;
    const latencies = [_]f64{ 0.94, 1.02, 0.87, 1.11, 0.99, 1.05 };

    var sum: f64 = 0;
    var sum_sq: f64 = 0;
    var minimum = latencies[0];
    var maximum = latencies[0];
    for (latencies) |value| {
        sum += value;
        sum_sq += value * value;
        minimum = @min(minimum, value);
        maximum = @max(maximum, value);
    }

    const mean = sum / @as(f64, @floatFromInt(latencies.len));
    const rms = m.sqrt(sum_sq / @as(f64, @floatFromInt(latencies.len)));
    const normalized = m.clamp((mean - 0.8) / 0.6, 0.0, 1.0);

    const turn_degrees: f64 = 72.0;
    const turn_radians = turn_degrees * m.rad_per_deg;
    const right_angle = m.pi / 2.0;
    const approx_right = m.approxEqRel(f64, turn_radians, right_angle, 1e-12);

    const hyp = m.hypot(3.0, 4.0);

    try stdout.print("sample count -> {d}\n", .{latencies.len});
    try stdout.print("min/max -> {d:.2} / {d:.2}\n", .{ minimum, maximum });
    try stdout.print("mean -> {d:.3}\n", .{mean});
    try stdout.print("rms -> {d:.3}\n", .{rms});
    try stdout.print("normalized mean -> {d:.3}\n", .{normalized});
    try stdout.print("72deg in rad -> {d:.6}\n", .{turn_radians});
    try stdout.print("close to right angle? -> {s}\n", .{if (approx_right) "yes" else "no"});
    try stdout.print("hypot(3,4) -> {d:.1}\n", .{hyp});
    try stdout.print("phi constant -> {d:.9}\n", .{m.phi});

    try stdout.flush();
}
Run
Shell
$ zig run math_inspector.zig
Output
Shell
sample count -> 6
min/max -> 0.87 / 1.11
mean -> 0.997
rms -> 1.000
normalized mean -> 0.328
72deg in rad -> 1.256637
close to right angle? -> no
hypot(3,4) -> 5.0
phi constant -> 1.618033989

对于大幅值比较更喜欢 approxEqRel,对于接近零的值更喜欢 approxEqAbs;两者都遵循IEEE-754边缘情况而不触发NaN。

容差、缩放和派生量

角度转换使用 rad_per_deg/deg_per_rad,而 hypot 通过避免灾难性抵消来保持毕达哥拉斯计算的精度。当链接转换时,即使您的公共API使用更窄的浮点数,也将中间结果保持在 f64 中——std.math 中的混合类型重载执行正确的操作并避免编译器警告。39

哈希:可重现性与完整性

Zig尖锐地分割哈希策略:std.hash 系列针对内存中桶的速度和低碰撞率,而 std.crypto.hash.sha2 提供用于完整性检查或签名管道的标准化摘要。

用于桶的非加密哈希

std.hash.Wyhash.hash 生成一个64位值,可以根据您的喜好进行播种,非常适合哈希映射或布隆过滤器,其中雪崩特性比对抗性阻力更重要。如果您需要具有编译时类型感知能力的结构化哈希,std.hash.autoHash 递归遍历您的字段并将它们输入到可配置的后端。44auto_hash.zig

带有实用护栏的SHA-256摘要管道

即使您的CLI只需要校验和,也将SHA-256视为完整性原语——而不是真实性保证——并为用户记录这种差异。

Zig
const std = @import("std");

pub fn main() !void {
    var stdout_buffer: [4096]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    const input_path = if (args.len > 1) args[1] else "payload.txt";

    var file = try std.fs.cwd().openFile(input_path, .{ .mode = .read_only });
    defer file.close();

    var sha256 = std.crypto.hash.sha2.Sha256.init(.{});
    var buffer: [4096]u8 = undefined;
    while (true) {
        const read = try file.read(&buffer);
        if (read == 0) break;
        sha256.update(buffer[0..read]);
    }

    var digest: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined;
    sha256.final(&digest);

    const sample = "payload preview";
    const wyhash = std.hash.Wyhash.hash(0, sample);

    try stdout.print("wyhash(seed=0) {s} -> 0x{x:0>16}\n", .{ sample, wyhash });
    const hex_digest = std.fmt.bytesToHex(digest, .lower);
    try stdout.print("sha256({s}) ->\n  {s}\n", .{ input_path, hex_digest });
    try stdout.print("(remember: sha256 certifies integrity, not authenticity.)\n", .{});

    try stdout.flush();
}
Run
Shell
$ zig run hash_digest_tool.zig -- chapters-data/code/50__random-and-math/payload.txt
Output
Shell
wyhash(seed=0) payload preview -> 0x30297ecbb2bd0c02
sha256(chapters-data/code/50__random-and-math/payload.txt) ->
  0498ca2116fb55b7a502d0bf3ad5d0e0b3f4e23ad919bdc0f9f151ca3637a6fa
(remember: sha256 certifies integrity, not authenticity.)

对大文件进行哈希时,通过可重用缓冲区流式传输,并重用单个竞技场分配器进行参数解析,以避免搅动通用分配器。10fmt.zig

注意事项与警告

  • Random 结构不是线程安全的;为每个工作程序拆分不同的生成器或用原子保护访问,以避免共享状态竞争。29
  • std.math 函数遵循IEEE-754 NaN传播——在无效操作后绝不依赖比较而没有显式检查。
  • 加密摘要应与签名检查、HMAC或可信分布配对;单独的SHA-256检测损坏,而不是篡改。hash_composition.zig

练习

  • 在第一个示例中将 DefaultPrng 替换为 std.Random.DefaultCsprng 并测量跨构建模式的性能差异。39ChaCha.zig
  • 扩展 math_inspector.zig 以使用 approxEqRel 计算置信区间,以标记延迟报告中的异常值。47
  • 修改 hash_digest_tool.zig 以计算和存储TAR档案中每个文件的SHA-256摘要,该档案来自第49章,并随档案一起发出清单。tar.zig

警告、替代方案和边缘情况

  • Xoshiro上的跳跃函数不可逆地改变状态;如果您以后需要回退,请在调用 jump() 之前快照您的生成器。
  • 避免在巨大文件上使用 bytesToHex 进行流式输出——更喜欢增量编码器以避开大型堆栈分配。
  • 巨大文件(>4 GiB)的SHA-256摘要必须考虑特定于平台的路径编码;在管道中更早地对UTF-8/UTF-16进行标准化,以避免对不同的字节流进行哈希。45

Help make this chapter better.

Found a typo, rough edge, or missing explanation? Open an issue or propose a small improvement on GitHub.