Skip to content

Entropy Visualizer — C# source

Visualize the randomness quality of any data. See Shannon entropy, byte frequency distribution, chi-squared score, and a visual entropy heatmap.

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

// Entropy Visualizer - pure byte-level randomness analysis. No DOM, no deps.
//
// Language: C# 12 / .NET 8 (standard library only)
// Source:   CosmoDev polyglot showcase port of the Entropy Visualizer tool,
//           ported from src/lib/entropy-visualizer.ts (the canonical
//           TypeScript implementation).
// License:  display source - part of CosmoDev's polyglot tool pages.
//
// Everything is deterministic: the same bytes always produce the same numbers.

public enum EntropyVerdict
{
    Excellent,
    Good,
    Suspicious,
    Low,
}

/// <summary>The full analysis of one byte sequence.</summary>
/// <param name="ShannonEntropy">Shannon entropy in bits per byte (0 = one repeating byte, 8 = perfectly uniform).</param>
/// <param name="ChiSquared">Chi-squared statistic against the uniform 256-bin expectation.</param>
/// <param name="ChiSquaredPValue">Upper-tail p-value for chi-squared with 255 degrees of freedom (1 = perfectly plausible).</param>
/// <param name="SerialCorrelation">Circular serial correlation between consecutive bytes (-1..+1, 0 = uncorrelated).</param>
/// <param name="MonteCarloPi">Monte Carlo pi estimate from consecutive byte pairs (~3.14159 for random data).</param>
/// <param name="ByteFrequencies">Occurrence count per byte value 0-255 (always 256 entries).</param>
/// <param name="BlockEntropies">Shannon entropy (bits/byte) of each BlockSize-byte block, for the heatmap.</param>
public sealed record EntropyAnalysis(
    double ShannonEntropy,
    double ChiSquared,
    double ChiSquaredPValue,
    double SerialCorrelation,
    double MonteCarloPi,
    int[] ByteFrequencies,
    double[] BlockEntropies,
    EntropyVerdict Verdict);

public static class EntropyVisualizer
{
    /// <summary>Bytes per heatmap block.</summary>
    public const int BlockSize = 16;

    /// <summary>Degrees of freedom for the byte-frequency chi-squared test (256 bins - 1 constraint).</summary>
    public const int ChiSquaredDf = 255;

    /// <summary>Count occurrences of each byte value 0-255.</summary>
    public static int[] CountBytes(byte[] data)
    {
        var freq = new int[256];
        foreach (byte b in data) freq[b]++;
        return freq;
    }

    /// <summary>
    /// Shannon entropy H = -sum of p(x) * log2(p(x)) in bits per byte, computed
    /// from a frequency histogram. Zero-count bins contribute nothing.
    /// <paramref name="total"/> must be the sum of <paramref name="frequencies"/>.
    /// </summary>
    public static double ShannonBitsPerByte(int[] frequencies, int total)
    {
        if (total <= 0) return 0;
        double h = 0;
        foreach (int count in frequencies)
        {
            if (count == 0) continue;
            double p = (double)count / total;
            h -= p * Math.Log2(p);
        }
        return h;
    }

    /// <summary>
    /// Pearson chi-squared comparing observed byte counts against a uniform
    /// expectation E = total/256 per bin.
    /// </summary>
    public static double ChiSquaredStatistic(int[] frequencies, int total)
    {
        if (total <= 0) throw new ArgumentException("chi-squared needs a positive sample size");
        double expected = total / 256.0;
        double chi2 = 0;
        foreach (int observed in frequencies)
        {
            double diff = observed - expected;
            chi2 += diff * diff / expected;
        }
        return chi2;
    }

    /// <summary>Lanczos approximation (g=7, 9 coefficients) of ln Gamma(x).</summary>
    private static double LnGamma(double x)
    {
        double[] g =
        {
            0.99999999999980993, 676.5203681218851, -1259.1392167224028,
            771.32342877765313, -176.61502916214059, 12.507343278686905,
            -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7,
        };
        if (x < 0.5)
        {
            // Reflection formula: Gamma(x) * Gamma(1-x) = pi / sin(pi*x)
            return Math.Log(Math.PI / Math.Sin(Math.PI * x)) - LnGamma(1 - x);
        }
        x -= 1;
        double a = g[0];
        double t = x + 7.5;
        for (int i = 1; i < 9; i++) a += g[i] / (x + i);
        return 0.5 * Math.Log(2 * Math.PI) + (x + 0.5) * Math.Log(t) - t + Math.Log(a);
    }

    /// <summary>
    /// Regularized upper incomplete gamma function Q(a, x) = Gamma(a,x)/Gamma(a),
    /// via the power series (x &lt; a+1) or the Lentz continued fraction
    /// (otherwise). Numerical Recipes section 6.2.
    /// </summary>
    public static double GammaQ(double a, double x)
    {
        if (a <= 0 || x < 0) return double.NaN;
        if (x == 0) return 1;
        if (x < a + 1)
        {
            // Series for P(a,x); Q = 1 - P
            double ap = a;
            double sum = 1 / a;
            double del = sum;
            for (int n = 0; n < 1000; n++)
            {
                ap += 1;
                del *= x / ap;
                sum += del;
                if (Math.Abs(del) < Math.Abs(sum) * 1e-15) break;
            }
            return Math.Min(1, Math.Max(0, 1 - sum * Math.Exp(-x + a * Math.Log(x) - LnGamma(a))));
        }
        // Continued fraction for Q(a,x)
        const double Fpmin = 1e-300;
        double b = x + 1 - a;
        double c = 1 / Fpmin;
        double d = 1 / b;
        double h = d;
        for (int i = 1; i <= 1000; i++)
        {
            double an = -i * (i - a);
            b += 2;
            d = an * d + b;
            if (Math.Abs(d) < Fpmin) d = Fpmin;
            c = b + an / c;
            if (Math.Abs(c) < Fpmin) c = Fpmin;
            d = 1 / d;
            double del = d * c;
            h *= del;
            if (Math.Abs(del - 1) < 1e-15) break;
        }
        return Math.Min(1, Math.Max(0, Math.Exp(-x + a * Math.Log(x) - LnGamma(a)) * h));
    }

    /// <summary>Upper-tail p-value for a chi-squared statistic with df degrees of freedom.</summary>
    public static double ChiSquaredP(double chi2, int df = ChiSquaredDf)
    {
        if (chi2 < 0 || df <= 0) return double.NaN;
        return GammaQ(df / 2.0, chi2 / 2);
    }

    /// <summary>
    /// Circular serial correlation between consecutive bytes (the `ent` tool's
    /// metric): scc = (sumXY - (sumX)^2/n) / (sumSq - (sumX)^2/n) over the pair
    /// sequence (x0,x1), (x1,x2), ..., (xn-1,x0). 0 = uncorrelated, +/-1 =
    /// perfectly (anti)correlated. Constant input has a zero denominator and is
    /// reported as 0 (nothing to correlate); inputs shorter than 2 bytes are
    /// also 0.
    /// </summary>
    public static double SerialCorrelationCoefficient(byte[] data)
    {
        int n = data.Length;
        if (n < 2) return 0;
        long sum = 0;
        long sumSq = 0;
        long sumXY = 0;
        for (int i = 0; i < n; i++)
        {
            int x = data[i];
            int y = data[(i + 1) % n];
            sum += x;
            sumSq += x * x;
            sumXY += x * y;
        }
        double meanSq = (double)(sum * sum) / n;
        double denom = sumSq - meanSq;
        if (denom == 0) return 0;
        return (sumXY - meanSq) / denom;
    }

    /// <summary>
    /// Monte Carlo pi estimate: consecutive byte pairs are (x, y) points in a
    /// 256x256 square; the fraction inside the inscribed circle (center 127.5,
    /// radius 128) times 4 estimates pi. Inputs with fewer than 2 bytes give 0.
    /// </summary>
    public static double MonteCarloPiEstimate(byte[] data)
    {
        int pairs = data.Length / 2;
        if (pairs == 0) return 0;
        int inside = 0;
        for (int i = 0; i < pairs; i++)
        {
            double dx = data[2 * i] - 127.5;
            double dy = data[2 * i + 1] - 127.5;
            if (dx * dx + dy * dy <= 128 * 128) inside++;
        }
        return (4.0 * inside) / pairs;
    }

    /// <summary>Shannon entropy (bits/byte) of each consecutive blockSize-byte block.</summary>
    public static double[] BlockEntropies(byte[] data, int blockSize = BlockSize)
    {
        if (blockSize < 1) throw new ArgumentException("blockSize must be at least 1");
        var blocks = new List<double>();
        for (int off = 0; off < data.Length; off += blockSize)
        {
            var counts = new int[256];
            int n = 0;
            int end = Math.Min(off + blockSize, data.Length);
            for (int i = off; i < end; i++)
            {
                counts[data[i]]++;
                n++;
            }
            blocks.Add(ShannonBitsPerByte(counts, n));
        }
        return blocks.ToArray();
    }

    /// <summary>Map a Shannon entropy (bits/byte) to the verdict scale.</summary>
    public static EntropyVerdict VerdictFromShannon(double bitsPerByte) => bitsPerByte switch
    {
        > 7.5 => EntropyVerdict.Excellent,
        > 6.0 => EntropyVerdict.Good,
        > 4.0 => EntropyVerdict.Suspicious,
        _ => EntropyVerdict.Low,
    };

    /// <summary>
    /// Full analysis of a byte sequence. Throws on empty input - there is
    /// nothing to measure and every metric would be undefined.
    /// </summary>
    public static EntropyAnalysis AnalyzeEntropy(byte[] data)
    {
        if (data.Length == 0)
        {
            throw new ArgumentException("Nothing to analyze - provide at least 1 byte of data.");
        }
        int total = data.Length;
        int[] freq = CountBytes(data);
        double shannon = ShannonBitsPerByte(freq, total);
        double chi2 = ChiSquaredStatistic(freq, total);
        return new EntropyAnalysis(
            ShannonEntropy: shannon,
            ChiSquared: chi2,
            ChiSquaredPValue: ChiSquaredP(chi2),
            SerialCorrelation: SerialCorrelationCoefficient(data),
            MonteCarloPi: MonteCarloPiEstimate(data),
            ByteFrequencies: freq,
            BlockEntropies: BlockEntropies(data),
            Verdict: VerdictFromShannon(shannon));
    }
}

Also available in 8 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 →