Skip to content

Entropy Visualizer — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Entropy Visualizer — pure byte-level randomness analysis. No deps.
//
// Language: Java (17+, standard library only)
// Ported from src/lib/entropy-visualizer.ts
// display source — part of CosmoDev's polyglot tool pages.
//
// Everything is deterministic: the same bytes always produce the same numbers.
// Java doubles are IEEE-754 binary64, exactly the arithmetic the TS reference
// uses, so every metric matches to the bit.

import java.util.ArrayList;
import java.util.List;

public final class EntropyVisualizer {

    public enum EntropyVerdict { EXCELLENT, GOOD, SUSPICIOUS, LOW }

    /** Full analysis of a byte sequence (byteFrequencies is always 256 entries). */
    public record EntropyAnalysis(
        /** Shannon entropy in bits per byte (0 = one repeating byte, 8 = perfectly uniform). */
        double shannonEntropy,
        /** χ² statistic against the uniform 256-bin expectation. */
        double chiSquared,
        /** Upper-tail p-value for χ² with 255 degrees of freedom (1 = perfectly plausible). */
        double chiSquaredPValue,
        /** Circular serial correlation between consecutive bytes (-1 … +1, 0 = uncorrelated). */
        double serialCorrelation,
        /** Monte Carlo π estimate from consecutive byte pairs (≈3.14159 for random data). */
        double monteCarloPi,
        /** Occurrence count per byte value 0-255. */
        int[] byteFrequencies,
        /** Shannon entropy (bits/byte) of each BLOCK_SIZE-byte block, for the heatmap. */
        double[] blockEntropies,
        EntropyVerdict verdict) {
    }

    /** Bytes per heatmap block. */
    public static final int BLOCK_SIZE = 16;

    /** Degrees of freedom for the byte-frequency χ² test (256 bins - 1 constraint). */
    public static final int CHI_SQUARED_DF = 255;

    private static final double LN2 = Math.log(2);

    /** Count occurrences of each byte value 0-255. */
    public static int[] countBytes(byte[] data) {
        int[] freq = new int[256];
        for (byte b : data) freq[b & 0xff]++;
        return freq;
    }

    /**
     * Shannon entropy H = -Σ p(x)·log2(p(x)) in bits per byte, computed from a
     * frequency histogram. Zero-count bins contribute nothing. {@code total}
     * must be the sum of {@code frequencies}.
     */
    public static double shannonBitsPerByte(int[] frequencies, long total) {
        if (total <= 0) return 0;
        double h = 0;
        for (int count : frequencies) {
            if (count == 0) continue;
            double p = count / (double) total;
            h -= p * (Math.log(p) / LN2);
        }
        return h;
    }

    /**
     * Pearson χ² comparing observed byte counts against a uniform expectation
     * E = total/256 per bin.
     */
    public static double chiSquaredStatistic(int[] frequencies, long total) {
        if (total <= 0) throw new IllegalArgumentException("chi-squared needs a positive sample size");
        double expected = total / 256.0;
        double chi2 = 0;
        for (int observed : frequencies) {
            double diff = observed - expected;
            chi2 += (diff * diff) / expected;
        }
        return chi2;
    }

    /** Lanczos approximation (g=7, 9 coefficients) of ln Γ(x). */
    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: Γ(x)·Γ(1-x) = π / sin(π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);
    }

    /**
     * Regularized upper incomplete gamma function Q(a, x) = Γ(a,x)/Γ(a), via the
     * power series (x < a+1) or the Lentz continued fraction (otherwise).
     * Numerical Recipes §6.2. Returns NaN for a <= 0 or x < 0.
     */
    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)
        final 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));
    }

    /** Upper-tail p-value for a χ² statistic with {@code df} degrees of freedom. */
    public static double chiSquaredP(double chi2, int df) {
        if (chi2 < 0 || df <= 0) return Double.NaN;
        return gammaQ(df / 2.0, chi2 / 2);
    }

    /** Upper-tail p-value for a χ² statistic with the default 255 degrees of freedom. */
    public static double chiSquaredP(double chi2) {
        return chiSquaredP(chi2, CHI_SQUARED_DF);
    }

    /**
     * Circular serial correlation between consecutive bytes (the {@code ent}
     * tool's metric): scc = (Σxy - (Σx)²/n) / (Σx² - (Σx)²/n) over the pair
     * sequence (x₀,x₁), (x₁,x₂), …, (xₙ₋₁,x₀). 0 = uncorrelated, ±1 = perfectly
     * (anti)correlated. Constant input has a zero denominator → reported as 0
     * (nothing to correlate); inputs shorter than 2 bytes are also 0.
     */
    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] & 0xff;
            int y = data[(i + 1) % n] & 0xff;
            sum += x;
            sumSq += (long) x * x;
            sumXY += (long) x * y;
        }
        double meanSq = (sum * (double) sum) / n;
        double denom = sumSq - meanSq;
        if (denom == 0) return 0;
        return (sumXY - meanSq) / denom;
    }

    /**
     * Monte Carlo π estimate: consecutive byte pairs are (x, y) points in a
     * 256×256 square; the fraction inside the inscribed circle (center 127.5,
     * radius 128) times 4 estimates π. Inputs with fewer than 2 bytes → 0.
     */
    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] & 0xff) - 127.5;
            double dy = (data[2 * i + 1] & 0xff) - 127.5;
            if (dx * dx + dy * dy <= 128 * 128) inside++;
        }
        return (4.0 * inside) / pairs;
    }

    /** Shannon entropy (bits/byte) of each consecutive {@code blockSize}-byte block. */
    public static double[] blockEntropies(byte[] data, int blockSize) {
        if (blockSize < 1) throw new IllegalArgumentException("blockSize must be at least 1");
        List<Double> blocks = new ArrayList<>();
        int[] counts = new int[256];
        for (int off = 0; off < data.length; off += blockSize) {
            java.util.Arrays.fill(counts, 0);
            int n = 0;
            int end = Math.min(off + blockSize, data.length);
            for (int i = off; i < end; i++) {
                counts[data[i] & 0xff]++;
                n++;
            }
            blocks.add(shannonBitsPerByte(counts, n));
        }
        return blocks.stream().mapToDouble(Double::doubleValue).toArray();
    }

    /** Block entropies with the default 16-byte blocks. */
    public static double[] blockEntropies(byte[] data) {
        return blockEntropies(data, BLOCK_SIZE);
    }

    /** Map a Shannon entropy (bits/byte) to the verdict scale. */
    public static EntropyVerdict verdictFromShannon(double bitsPerByte) {
        if (bitsPerByte > 7.5) return EntropyVerdict.EXCELLENT;
        if (bitsPerByte > 6.0) return EntropyVerdict.GOOD;
        if (bitsPerByte > 4.0) return EntropyVerdict.SUSPICIOUS;
        return EntropyVerdict.LOW;
    }

    /**
     * Full analysis of a byte sequence. Throws on empty input — there is
     * nothing to measure and every metric would be undefined.
     */
    public static EntropyAnalysis analyzeEntropy(byte[] data) {
        if (data.length == 0) {
            throw new IllegalArgumentException("Nothing to analyze - provide at least 1 byte of data.");
        }
        long total = data.length;
        int[] freq = countBytes(data);
        double shannon = shannonBitsPerByte(freq, total);
        double chi2 = chiSquaredStatistic(freq, total);
        return new EntropyAnalysis(
            shannon,
            chi2,
            chiSquaredP(chi2),
            serialCorrelationCoefficient(data),
            monteCarloPiEstimate(data),
            freq,
            blockEntropies(data),
            verdictFromShannon(shannon));
    }

    private EntropyVisualizer() {
    }
}

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 →