use crate::Stake;
#[cfg(any(feature = "num-integer-backend", target_family = "wasm", windows))]
use {
num_bigint::{BigInt, Sign},
num_rational::Ratio,
num_traits::{One, Signed},
std::ops::Neg,
};
#[cfg(any(
feature = "num-integer-backend",
target_family = "wasm",
target_env = "musl",
windows
))]
pub(crate) fn is_lottery_won(phi_f: f64, ev: [u8; 64], stake: Stake, total_stake: Stake) -> bool {
if (phi_f - 1.0).abs() < f64::EPSILON {
return true;
}
let ev_max = BigInt::from(2u8).pow(512);
let ev = BigInt::from_bytes_le(Sign::Plus, &ev);
let q = Ratio::new_raw(ev_max.clone(), ev_max - ev);
let c =
Ratio::from_float((1.0 - phi_f).ln()).expect("Only fails if the float is infinite or NaN.");
let w = Ratio::new_raw(BigInt::from(stake), BigInt::from(total_stake));
let x = (w * c).neg();
taylor_comparison(1000, q, x)
}
#[cfg(any(
feature = "num-integer-backend",
target_family = "wasm",
target_env = "musl",
windows
))]
#[allow(clippy::redundant_clone)]
fn taylor_comparison(bound: usize, cmp: Ratio<BigInt>, x: Ratio<BigInt>) -> bool {
let mut new_x = x.clone();
let mut phi: Ratio<BigInt> = One::one();
let mut divisor: BigInt = One::one();
for _ in 0..bound {
phi += new_x.clone();
divisor += 1;
new_x = (new_x.clone() * x.clone()) / divisor.clone();
let error_term = new_x.clone().abs() * BigInt::from(3);
if cmp > (phi.clone() + error_term.clone()) {
return false;
} else if cmp < phi.clone() - error_term.clone() {
return true;
}
}
false
}
#[cfg(not(any(
feature = "num-integer-backend",
target_family = "wasm",
target_env = "musl",
windows
)))]
pub(crate) fn is_lottery_won(phi_f: f64, ev: [u8; 64], stake: Stake, total_stake: Stake) -> bool {
use rug::{Float, integer::Order, ops::Pow};
if (phi_f - 1.0).abs() < f64::EPSILON {
return true;
}
let ev = rug::Integer::from_digits(&ev, Order::LsfLe);
let ev_max: Float = Float::with_val(117, 2).pow(512);
let q = ev / ev_max;
let w = Float::with_val(117, stake) / Float::with_val(117, total_stake);
let phi = Float::with_val(117, 1.0) - Float::with_val(117, 1.0 - phi_f).pow(w);
q < phi
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::{BigInt, Sign};
use num_rational::Ratio;
use proptest::prelude::*;
fn trivial_is_lottery_won(phi_f: f64, ev: [u8; 64], stake: Stake, total_stake: Stake) -> bool {
let ev_max = BigInt::from(2u8).pow(512);
let ev = BigInt::from_bytes_le(Sign::Plus, &ev);
let q = Ratio::new_raw(ev, ev_max);
let w = stake as f64 / total_stake as f64;
let phi = Ratio::from_float(1.0 - (1.0 - phi_f).powf(w)).unwrap();
q < phi
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn is_lottery_won_check_precision_against_trivial_implementation(
phi_f in 0.01..0.5f64,
ev_1 in any::<[u8; 32]>(),
ev_2 in any::<[u8; 32]>(),
total_stake in 100_000_000..1_000_000_000u64,
stake in 1_000_000..50_000_000u64
) {
let mut ev = [0u8; 64];
ev.copy_from_slice(&[&ev_1[..], &ev_2[..]].concat());
let quick_result = trivial_is_lottery_won(phi_f, ev, stake, total_stake);
let result = is_lottery_won(phi_f, ev, stake, total_stake);
assert_eq!(quick_result, result);
}
#[cfg(any(feature = "num-integer-backend", target_family = "wasm", windows))]
#[test]
fn taylor_comparison_breaks_early(
x in -0.9..0.9f64,
) {
let exponential = num_traits::float::Float::exp(x);
let cmp_n = Ratio::from_float(exponential - 2e-10_f64).unwrap();
let cmp_p = Ratio::from_float(exponential + 2e-10_f64).unwrap();
assert!(taylor_comparison(1000, cmp_n, Ratio::from_float(x).unwrap()));
assert!(!taylor_comparison(1000, cmp_p, Ratio::from_float(x).unwrap()));
}
}
}