devela/num/prob/
probability.rs1use crate::{_impl_init, RatioU64, is, unwrap};
7
8#[doc = crate::_tags!(num)]
9#[doc = crate::_doc_meta!{
11 location("num/prob", struct Probability),
12 test_size_of(Probability = 16|128; niche Option),
13}]
14#[must_use]
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub struct Probability {
38 ratio: RatioU64,
39}
40
41_impl_init! { Self::ZERO => Probability }
42impl Default for Probability {
43 fn default() -> Self {
44 Self::ZERO
45 }
46}
47
48#[rustfmt::skip]
49impl Probability {
50 pub const ZERO: Self = Self { ratio: RatioU64::ZERO };
52 pub const ONE: Self = Self { ratio: RatioU64::ONE };
54
55 #[must_use]
61 pub const fn new(num: u64, den: u64) -> Option<Self> {
62 let ratio = unwrap![some? RatioU64::new(num, den)];
63 Self::from_ratio(ratio)
64 }
65 #[must_use]
71 pub const fn from_ratio(ratio: RatioU64) -> Option<Self> {
72 is! { ratio.num() > ratio.den(), return None }
73 Some(Self { ratio: ratio.reduced() })
74 }
75 #[must_use]
77 pub const fn as_ratio(&self) -> &RatioU64 { &self.ratio }
78
79 #[must_use]
81 pub const fn into_ratio(self) -> RatioU64 { self.ratio }
82
83 #[must_use]
85 pub const fn num(self) -> u64 { self.ratio.num() }
86
87 #[must_use]
89 pub const fn den(self) -> u64 { self.ratio.den() }
90
91 #[must_use]
93 pub const fn num_den(self) -> (u64, u64) { self.ratio.num_den() }
94
95 #[must_use]
97 pub const fn is_zero(self) -> bool { self.ratio.is_zero() }
98
99 #[must_use]
101 pub const fn is_one(self) -> bool { self.ratio.is_one() }
102
103 pub const fn complement(self) -> Self {
115 let (num, den) = self.ratio.num_den();
116 let ratio = RatioU64::new_nonzero(den - num, self.ratio.den_nonzero());
118 Self { ratio: ratio.reduced() }
119 }
120}
121
122#[cfg(test)]
123mod _test {
124 use super::*;
125
126 const HALF: Probability = match Probability::new(2, 4) {
127 Some(p) => p,
128 None => panic!("valid probability"),
129 };
130
131 #[test]
132 fn probability_const_construction() {
133 assert_eq!(HALF.num_den(), (1, 2));
134 }
135 #[test]
136 fn probability_accepts_unit_interval() {
137 assert_eq!(Probability::new(0, 1), Some(Probability::ZERO));
138 assert_eq!(Probability::new(1, 1), Some(Probability::ONE));
139 assert!(Probability::new(1, 2).is_some());
140 assert!(Probability::new(5, 7).is_some());
141 }
142 #[test]
143 fn probability_rejects_invalid_terms() {
144 assert_eq!(Probability::new(0, 0), None);
145 assert_eq!(Probability::new(1, 0), None);
146 assert_eq!(Probability::new(2, 1), None);
147 assert_eq!(Probability::new(u64::MAX, 1), None);
148 }
149 #[test]
150 fn probability_canonicalizes_equivalent_ratios() {
151 let a = Probability::new(1, 2).unwrap();
152 let b = Probability::new(2, 4).unwrap();
153 let c = Probability::new(50, 100).unwrap();
154 assert_eq!(a, b);
155 assert_eq!(b, c);
156 assert_eq!(a.num_den(), (1, 2));
157 assert_eq!(b.num_den(), (1, 2));
158 assert_eq!(c.num_den(), (1, 2));
159 }
160 #[test]
161 fn probability_canonicalizes_zero_and_one() {
162 assert_eq!(Probability::new(0, 999).unwrap(), Probability::ZERO);
163 assert_eq!(Probability::new(999, 999).unwrap(), Probability::ONE);
164 assert_eq!(Probability::ZERO.num_den(), (0, 1));
165 assert_eq!(Probability::ONE.num_den(), (1, 1));
166 }
167 #[test]
168 fn probability_constructs_from_ratio() {
169 let ratio = RatioU64::new(6, 8).unwrap();
170 let probability = Probability::from_ratio(ratio).unwrap();
171 assert_eq!(probability.num_den(), (3, 4));
172 let invalid = RatioU64::new(5, 4).unwrap();
173 assert_eq!(Probability::from_ratio(invalid), None);
174 }
175 #[test]
176 fn probability_complement_is_exact() {
177 assert_eq!(Probability::new(1, 4).unwrap().complement(), Probability::new(3, 4).unwrap(),);
178 assert_eq!(Probability::ZERO.complement(), Probability::ONE);
179 assert_eq!(Probability::ONE.complement(), Probability::ZERO);
180 assert_eq!(HALF.complement(), HALF);
181 }
182}