Entropy Visualizer — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// entropy-visualizer — pure byte-level randomness analysis.
//
// Language: Swift 5.9+ (Foundation)
// 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.
// Shannon entropy, Pearson χ² with its upper-tail p-value (Lanczos ln Γ +
// Numerical Recipes gammaQ), the `ent` tool's circular serial correlation,
// and a Monte Carlo π estimate — all in Double arithmetic, matching the TS
// reference operation for operation.
import Foundation
// MARK: - Types
enum EntropyVerdict: String {
case excellent
case good
case suspicious
case low
}
/// The full analysis result (EntropyAnalysis in the TS reference).
struct EntropyAnalysis {
/// Shannon entropy in bits per byte (0 = one repeating byte, 8 = perfectly uniform).
let shannonEntropy: Double
/// χ² statistic against the uniform 256-bin expectation.
let chiSquared: Double
/// Upper-tail p-value for χ² with 255 degrees of freedom (1 = perfectly plausible).
let chiSquaredPValue: Double
/// Circular serial correlation between consecutive bytes (-1 … +1, 0 = uncorrelated).
let serialCorrelation: Double
/// Monte Carlo π estimate from consecutive byte pairs (≈3.14159 for random data).
let monteCarloPi: Double
/// Occurrence count per byte value 0-255 (always 256 entries).
let byteFrequencies: [Int]
/// Shannon entropy (bits/byte) of each BLOCK_SIZE-byte block, for the heatmap.
let blockEntropies: [Double]
let verdict: EntropyVerdict
}
/// Bytes per heatmap block.
let BLOCK_SIZE = 16
/// Degrees of freedom for the byte-frequency χ² test (256 bins - 1 constraint).
let CHI_SQUARED_DF = 255
// MARK: - Histogram + entropy
/// Count occurrences of each byte value 0-255.
func countBytes(_ data: [UInt8]) -> [Int] {
var freq = [Int](repeating: 0, count: 256)
for b in data { freq[Int(b)] += 1 }
return freq
}
/// Shannon entropy H = -Σ p(x)·log2(p(x)) in bits per byte, computed from a
/// frequency histogram. Zero-count bins contribute nothing. `total` must be
/// the sum of `frequencies`.
func shannonBitsPerByte(_ frequencies: [Int], _ total: Int) -> Double {
if total <= 0 { return 0 }
var h = 0.0
for count in frequencies where count != 0 {
let p = Double(count) / Double(total)
h -= p * log2(p)
}
return h
}
/// Pearson χ² comparing observed byte counts against a uniform expectation
/// E = total/256 per bin.
func chiSquaredStatistic(_ frequencies: [Int], _ total: Int) throws -> Double {
if total <= 0 { throw NSError(domain: "entropy", code: 1,
userInfo: [NSLocalizedDescriptionKey: "chi-squared needs a positive sample size"]) }
let expected = Double(total) / 256
var chi2 = 0.0
for observed in frequencies {
let diff = Double(observed) - expected
chi2 += diff * diff / expected
}
return chi2
}
// MARK: - χ² p-value
/// Lanczos approximation (g=7, 9 coefficients) of ln Γ(x).
func lnGamma(_ x: Double) -> Double {
let g: [Double] = [
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 log(.pi / sin(.pi * x)) - lnGamma(1 - x)
}
let x = x - 1
var a = g[0]
let t = x + 7.5
for i in 1..<9 { a += g[i] / (x + Double(i)) }
return 0.5 * log(2 * .pi) + (x + 0.5) * log(t) - t + 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. (NaN inputs mirror the TS reference: non-finite.)
func gammaQ(_ a: Double, _ x: Double) -> Double {
if a <= 0 || x.isNaN || a.isNaN { return Double.nan }
if x < 0 { return Double.nan }
if x == 0 { return 1 }
if x < a + 1 {
// Series for P(a,x); Q = 1 - P
var ap = a
var sum = 1 / a
var del = sum
for _ in 0..<1000 {
ap += 1
del *= x / ap
sum += del
if abs(del) < abs(sum) * 1e-15 { break }
}
return min(1, max(0, 1 - sum * exp(-x + a * log(x) - lnGamma(a))))
}
// Continued fraction for Q(a,x)
let FPMIN = 1e-300
var b = x + 1 - a
var c = 1 / FPMIN
var d = 1 / b
var h = d
for i in 1...1000 {
let an = -Double(i) * (Double(i) - a)
b += 2
d = an * d + b
if abs(d) < FPMIN { d = FPMIN }
c = b + an / c
if abs(c) < FPMIN { c = FPMIN }
d = 1 / d
let del = d * c
h *= del
if abs(del - 1) < 1e-15 { break }
}
return min(1, max(0, exp(-x + a * log(x) - lnGamma(a)) * h))
}
/// Upper-tail p-value for a χ² statistic with `df` degrees of freedom.
func chiSquaredP(_ chi2: Double, df: Int = CHI_SQUARED_DF) -> Double {
if chi2 < 0 || df <= 0 || chi2.isNaN { return Double.nan }
return gammaQ(Double(df) / 2, chi2 / 2)
}
// MARK: - Correlation + Monte Carlo
/// Circular serial correlation between consecutive bytes (the `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.
func serialCorrelationCoefficient(_ data: [UInt8]) -> Double {
let n = data.count
if n < 2 { return 0 }
var sum = 0.0
var sumSq = 0.0
var sumXY = 0.0
for i in 0..<n {
let x = Double(data[i])
let y = Double(data[(i + 1) % n])
sum += x
sumSq += x * x
sumXY += x * y
}
let meanSq = sum * sum / Double(n)
let 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.
func monteCarloPiEstimate(_ data: [UInt8]) -> Double {
let pairs = data.count / 2
if pairs == 0 { return 0 }
var inside = 0
for i in 0..<pairs {
let dx = Double(data[2 * i]) - 127.5
let dy = Double(data[2 * i + 1]) - 127.5
if dx * dx + dy * dy <= 128 * 128 { inside += 1 }
}
return Double(4 * inside) / Double(pairs)
}
/// Shannon entropy (bits/byte) of each consecutive `blockSize`-byte block.
func blockEntropies(_ data: [UInt8], blockSize: Int = BLOCK_SIZE) throws -> [Double] {
if blockSize < 1 { throw NSError(domain: "entropy", code: 2,
userInfo: [NSLocalizedDescriptionKey: "blockSize must be at least 1"]) }
var blocks: [Double] = []
var off = 0
while off < data.count {
var counts = [Int](repeating: 0, count: 256)
var n = 0
let end = min(off + blockSize, data.count)
for i in off..<end {
counts[Int(data[i])] += 1
n += 1
}
blocks.append(shannonBitsPerByte(counts, n))
off += blockSize
}
return blocks
}
/// Map a Shannon entropy (bits/byte) to the verdict scale.
func verdictFromShannon(_ bitsPerByte: Double) -> EntropyVerdict {
if bitsPerByte > 7.5 { return .excellent }
if bitsPerByte > 6.0 { return .good }
if bitsPerByte > 4.0 { return .suspicious }
return .low
}
// MARK: - Analysis
/// Full analysis of a byte sequence. Throws on empty input — there is nothing
/// to measure and every metric would be undefined.
func analyzeEntropy(_ data: [UInt8]) throws -> EntropyAnalysis {
if data.isEmpty {
throw NSError(domain: "entropy", code: 3,
userInfo: [NSLocalizedDescriptionKey: "Nothing to analyze - provide at least 1 byte of data."])
}
let total = data.count
let freq = countBytes(data)
let shannon = shannonBitsPerByte(freq, total)
let chi2 = try chiSquaredStatistic(freq, total)
return EntropyAnalysis(
shannonEntropy: shannon,
chiSquared: chi2,
chiSquaredPValue: chiSquaredP(chi2),
serialCorrelation: serialCorrelationCoefficient(data),
monteCarloPi: monteCarloPiEstimate(data),
byteFrequencies: freq,
blockEntropies: try 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 →