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 < 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 →