use super::bit_context::BitContext;
use super::{EntropyCoder, EntropyDecoder};
use std::num::NonZeroU8;
pub(crate) trait SymbolCoder: EntropyCoder {
fn encode_symbol(&mut self, range: SymbolRange);
}
pub(crate) trait SymbolDecoder: EntropyDecoder {
const SPECULATES: bool;
fn decode_symbol_step(&mut self, walk: impl FnOnce(u32) -> (SymbolRange, usize)) -> usize;
}
pub(crate) const SHIFT: u8 = 8;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Probability {
pub(crate) prob: NonZeroU8,
}
impl Probability {
pub const fn new(trues: u64, falses: u64) -> Self {
let prob = if falses == 0 {
256 / (2 + trues)
} else if trues == 0 {
(1 + falses) * 256 / (2 + falses)
} else {
falses * 256 / (trues + falses)
};
let prob = prob as u8;
Probability {
prob: NonZeroU8::new(prob).unwrap(),
}
}
#[inline]
pub fn likely_bit(&self) -> bool {
self.prob.get() < (1 << (SHIFT - 1))
}
#[inline]
pub fn as_f64(self) -> f64 {
self.prob.get() as f64 / (1_u64 << SHIFT) as f64
}
}
impl std::fmt::Debug for Probability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let trues = 256 - self.prob.get() as u64;
let falses = self.prob;
write!(f, "Probability::new({trues},{falses})")
}
}
impl std::fmt::Display for Probability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let v = self.prob.get() as f64 / (1_u64 << SHIFT) as f64;
write!(f, "{v}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SymbolRange {
start: u32,
width: u32,
}
impl SymbolRange {
pub const BITS: u32 = 16;
pub const M: u32 = 1 << Self::BITS;
#[inline]
pub(crate) fn start(self) -> u32 {
self.start
}
#[inline]
pub(crate) fn width(self) -> u32 {
self.width
}
#[cfg(test)]
pub(crate) fn test_new(start: u32, width: u32) -> Self {
assert!(width >= 1 && start + width <= Self::M);
Self { start, width }
}
#[inline]
pub(crate) fn full() -> Self {
Self {
start: 0,
width: Self::M,
}
}
#[inline]
pub(crate) fn split_reserving(self, p: Probability, lo: u32, hi: u32) -> u32 {
debug_assert!(self.width >= lo + hi);
const { assert!(Self::BITS + 8 <= u32::BITS) };
(((self.width - lo - hi) * p.prob.get() as u32) >> 8) + lo
}
#[inline]
pub(crate) fn lower(self, split: u32) -> Self {
Self {
start: self.start,
width: split,
}
}
#[inline]
pub(crate) fn upper(self, split: u32) -> Self {
Self {
start: self.start + split,
width: self.width - split,
}
}
#[inline]
pub(crate) fn contains(self, slot: u32) -> bool {
debug_assert!(slot >= self.start);
slot - self.start < self.width
}
}
#[derive(Clone, Copy)]
pub(crate) struct BitModel {
pub(crate) next: [BitContext; 2],
pub(crate) prob: Probability,
}
impl BitModel {
const fn new(state: BitContext) -> Self {
Self {
next: [state.adapt(false), state.adapt(true)],
prob: state.probability(),
}
}
}
static FUSED: [BitModel; BitContext::COUNT] = {
let start = BitContext::True0False0;
let mut table = [BitModel::new(start); BitContext::COUNT];
let mut queued = [false; BitContext::COUNT];
let mut queue = [start; BitContext::COUNT];
queued[start as usize] = true;
let (mut head, mut tail) = (0, 1);
while head < tail {
let state = queue[head];
head += 1;
let entry = BitModel::new(state);
table[state as usize] = entry;
let mut j = 0;
while j < 2 {
let next = entry.next[j];
if !queued[next as usize] {
queued[next as usize] = true;
queue[tail] = next;
tail += 1;
}
j += 1;
}
}
table
};
impl BitContext {
#[inline(always)]
pub(crate) fn model(self) -> BitModel {
FUSED[self as usize]
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{distributions::Standard, prelude::*};
impl Distribution<Probability> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Probability {
let prob = rng.gen_range(1u8..255);
let prob = NonZeroU8::new(prob).unwrap();
Probability { prob }
}
}
#[test]
fn model_matches_tables() {
assert_eq!(BitContext::default(), BitContext::True0False0);
for _ in 0..20_000 {
let state: BitContext = rand::random();
let entry = state.model();
assert_eq!(entry.prob, state.probability());
assert_eq!(entry.next, [state.adapt(false), state.adapt(true)]);
}
}
}