Skip to content

Statistics Calculator — Zig source

Compute descriptive statistics - count, sum, mean, median, mode, min/max, range, variance, standard deviation, and quartiles (Q1/Q3/IQR) - from any list of numbers. Tolerates mixed separators and flags unparseable tokens. Choose sample (n−1) or population (n) variance. Everything runs 100% client-side.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// statistics — descriptive statistics over a free-form number list. Language: Zig (0.12+). Port of src/lib/statistics.ts — same logic as this dir's python.py: comma/whitespace parse (finite only), R-7 interpolated quantiles, mode (empty when uniform), sample vs population variance.
const std = @import("std");

const ParseResult = struct { values: []f64, invalid: [][]const u8 };

/// Split on comma/whitespace runs; only fully-parsed finite tokens count as values.
fn parseNumbers(alloc: std.mem.Allocator, input: []const u8) !ParseResult {
    var values = std.ArrayList(f64).init(alloc);
    var invalid = std.ArrayList([]const u8).init(alloc);
    var it = std.mem.tokenizeAny(u8, input, " ,\t\n\r");
    while (it.next()) |tok| {
        // parseFloat accepts inf/nan — filtered by isFinite below.
        const v = std.fmt.parseFloat(f64, tok) catch { try invalid.append(tok); continue; };
        if (std.math.isFinite(v)) try values.append(v) else try invalid.append(tok);
    }
    return .{ .values = try values.toOwnedSlice(), .invalid = try invalid.toOwnedSlice() };
}

/// Linear-interpolation quantile (R-7 / NumPy / Excel PERCENTILE) over ascending, non-empty data.
fn quantile(s: []const f64, p: f64) f64 {
    const h = @as(f64, @floatFromInt(s.len - 1)) * p;
    const lo: usize = @intFromFloat(@floor(h)); const hi: usize = @intFromFloat(@ceil(h));
    if (lo == hi) return s[lo];
    return s[lo] + (h - @as(f64, @floatFromInt(lo))) * (s[hi] - s[lo]);
}

/// Mode over an ascending array (equal runs are contiguous): one distinct value IS the mode; uniform spread → none.
fn modeSorted(s: []const f64, out: []f64) usize {
    var best: usize = 1; // every value occurs at least once
    var runs: usize = 0; var m: usize = 0; var i: usize = 0;
    while (i < s.len) { // pass 1: longest run of equal neighbours
        var j = i; while (j < s.len and s[j] == s[i]) j += 1;
        if (j - i > best) best = j - i;
        runs += 1; i = j;
    }
    if (runs == 1) { out[0] = s[0]; return 1; }
    var at_best: usize = 0; i = 0;
    while (i < s.len) { // pass 2: emit each run whose length hits the max
        var j = i; while (j < s.len and s[j] == s[i]) j += 1;
        if (j - i == best) { out[m] = s[i]; m += 1; at_best += 1; }
        i = j;
    }
    return if (at_best == runs) 0 else m;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();
    const parsed = try parseNumbers(alloc, "2, 4 4, 6, 8, 11, oops");
    defer alloc.free(parsed.values);
    defer alloc.free(parsed.invalid);
    var s = try alloc.dupe(f64, parsed.values); // sorted copy — the input order stays intact
    defer alloc.free(s);
    std.mem.sort(f64, s, {}, std.sort.asc(f64));
    var sum: f64 = 0;
    for (parsed.values) |v| sum += v; // sum in original order
    const n: f64 = @floatFromInt(parsed.values.len);
    const mean = sum / n;
    var ss: f64 = 0;
    for (parsed.values) |v| ss += (v - mean) * (v - mean);
    // Sample variance needs n >= 2; the population estimator always divides by n.
    const variance: f64 = if (parsed.values.len >= 2) ss / (n - 1) else std.math.nan(f64);
    var modes: [64]f64 = undefined;
    const m = modeSorted(s, &modes);
    const q1 = quantile(s, 0.25); const med = quantile(s, 0.5); const q3 = quantile(s, 0.75);
    std.debug.print("n={d} sum={d} mean={d}\nmedian={d} q1={d} q3={d} iqr={d}\nmin={d} max={d} range={d}\nvariance(sample)={d} stddev={d}\nmode=[", .{
        parsed.values.len, sum,                mean,  med, q1, q3, q3 - q1,
        s[0],           s[s.len - 1],          s[s.len - 1] - s[0], variance, @sqrt(variance),
    });
    for (modes[0..m], 0..) |v, i| { if (i != 0) std.debug.print(", ", .{}); std.debug.print("{d}", .{v}); }
    std.debug.print("]  invalid=[", .{});
    for (parsed.invalid, 0..) |tok, i| { if (i != 0) std.debug.print(", ", .{}); std.debug.print("{s}", .{tok}); }
    std.debug.print("]\n", .{});
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →