Files
brk/crates/brk_oracle/src/scale.rs
T
2026-05-24 18:40:35 +02:00

73 lines
2.3 KiB
Rust

use brk_types::{Histogram, Sats};
pub const BINS_PER_DECADE: usize = 200;
const MIN_LOG_BTC: i32 = -8;
const MAX_LOG_BTC: i32 = 4;
pub const NUM_BINS: usize = BINS_PER_DECADE * (MAX_LOG_BTC - MIN_LOG_BTC) as usize;
/// Per-block round-dollar payment counts, one `u32` per log-scale bin: the
/// oracle's ring-buffer element and the `histogram/raw/*` wire payload.
pub type HistogramRaw = Histogram<u32, NUM_BINS>;
/// Smoothed EMA over the window, one `f64` per bin. The stencil search reads it,
/// never serialized (projected to [`HistogramEmaCompact`] for the wire).
pub type HistogramEma = Histogram<f64, NUM_BINS>;
/// Quantized `u16` projection of [`HistogramEma`] for the `histogram/ema/*` wire.
pub type HistogramEmaCompact = Histogram<u16, NUM_BINS>;
/// Maps a satoshi value to its log-scale bin index.
/// bin = round(log10(sats) * BINS_PER_DECADE).
#[inline(always)]
pub fn sats_to_bin(sats: Sats) -> Option<usize> {
if sats.is_zero() {
return None;
}
let bin = ((*sats as f64).log10() * BINS_PER_DECADE as f64).round() as i64;
if bin >= 0 && (bin as usize) < NUM_BINS {
Some(bin as usize)
} else {
None
}
}
/// Converts a fractional bin to a USD price in cents.
/// For a $D output at price P: sats = D * 1e8 / P, so P = 10^(10 - bin/200) dollars,
/// where 10 = log10($100 reference * 1e8 sats/BTC).
#[inline]
pub fn bin_to_cents(bin: f64) -> u64 {
let dollars = 10.0_f64.powf(10.0 - bin / BINS_PER_DECADE as f64);
(dollars * 100.0).round() as u64
}
/// Converts a USD price in cents to a fractional bin (inverse of bin_to_cents).
#[inline]
pub fn cents_to_bin(cents: f64) -> f64 {
(10.0 - (cents / 100.0).log10()) * BINS_PER_DECADE as f64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sats_to_bin_round_trip() {
assert_eq!(sats_to_bin(Sats::new(100_000_000)), Some(1600));
assert_eq!(sats_to_bin(Sats::new(1)), Some(0));
assert_eq!(sats_to_bin(Sats::ZERO), None);
}
#[test]
fn bin_to_cents_known_values() {
assert_eq!(bin_to_cents(1600.0), 10000);
assert_eq!(bin_to_cents(1800.0), 1000);
}
#[test]
fn sats_to_bin_boundary() {
assert_eq!(sats_to_bin(Sats::new(1_000_000_000_000)), None);
let sats = 10.0_f64.powf(11.995) as u64;
assert!(sats_to_bin(Sats::new(sats)).is_some());
}
}