1#![cfg(feature = "std")]
2
3use crate::Uint;
4
5impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
6 #[inline]
10 #[must_use]
11 pub fn checked_log(self, base: Self) -> Option<usize> {
12 if base < Self::from(2) || self.is_zero() {
13 return None;
14 }
15 Some(self.log(base))
16 }
17
18 #[inline]
22 #[must_use]
23 pub fn checked_log10(self) -> Option<usize> {
24 self.checked_log(Self::from(10))
25 }
26
27 #[inline]
33 #[must_use]
34 pub fn checked_log2(self) -> Option<usize> {
35 self.checked_log(Self::from(2))
36 }
37
38 #[inline]
44 #[must_use]
45 pub fn log(self, base: Self) -> usize {
46 assert!(self != Self::ZERO);
47 assert!(base >= Self::from(2));
48 if base == Self::from(2) {
49 return self.bit_len() - 1;
50 }
51 if self < base {
52 return 0;
53 }
54
55 #[allow(clippy::cast_precision_loss)] let result = self.approx_log2() / base.approx_log2();
58 assert!(result.is_normal());
60 let mut result = result.try_into().unwrap();
61
62 loop {
65 if let Some(value) = base.checked_pow(result) {
66 if value > self {
67 assert!(result != Self::ZERO);
68 result -= Self::from(1);
69 continue;
70 }
71 } else {
72 result -= Self::from(1);
74 }
75 break;
76 }
77 while let Some(trial) = result.checked_add(Self::from(1)) {
78 if let Some(value) = base.checked_pow(trial) {
79 if value <= self {
80 result = trial;
81 continue;
82 }
83 }
84 break;
85 }
86
87 result.to()
89 }
90
91 #[inline]
97 #[must_use]
98 pub fn log10(self) -> usize {
99 self.log(Self::from(10))
100 }
101
102 #[inline]
108 #[must_use]
109 pub fn log2(self) -> usize {
110 self.log(Self::from(2))
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn approx_log(self, base: f64) -> f64 {
117 self.approx_log2() / base.log2()
118 }
119
120 #[inline]
134 #[must_use]
135 #[allow(clippy::cast_precision_loss)]
136 pub fn approx_log2(self) -> f64 {
137 let (bits, exp) = self.most_significant_bits();
142 let bits = bits as f64;
144 let exp = exp as f64;
145 bits.log2() + exp
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn approx_log10(self) -> f64 {
152 self.approx_log2() / core::f64::consts::LOG2_10
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::{aliases::U128, const_for, nlimbs};
160 use proptest::{prop_assume, proptest};
161
162 #[test]
163 fn test_checked_log2() {
164 assert_eq!(U128::from(0).checked_log2(), None);
165 assert_eq!(U128::from(1).checked_log2(), Some(0));
166 assert_eq!(U128::from(2).checked_log2(), Some(1));
167 assert_eq!(U128::from(3).checked_log2(), Some(1));
168 assert_eq!(U128::from(127).checked_log2(), Some(6));
169 assert_eq!(U128::from(128).checked_log2(), Some(7));
170 }
171
172 #[test]
173 fn test_approx_log2_pow2() {
174 const_for!(BITS in SIZES {
175 const LIMBS: usize = nlimbs(BITS);
176 type U = Uint<BITS, LIMBS>;
177 proptest!(|(value: U)| {
178 let log = value.approx_log2();
179 let pow = U::approx_pow2(log).unwrap();
180 let error = value.abs_diff(pow);
181 let correct_bits = value.bit_len() - error.bit_len();
182 assert!(correct_bits == value.bit_len() || correct_bits >= 42);
186 });
187 });
188 }
189
190 #[test]
191 fn test_pow_log() {
192 const_for!(BITS in NON_ZERO if (BITS >= 64) {
193 const LIMBS: usize = nlimbs(BITS);
194 type U = Uint<BITS, LIMBS>;
195 proptest!(|(b in 2_u64..100, e in 0..BITS)| {
196 if let Some(value) = U::from(b).checked_pow(U::from(e)) {
197 assert!(value > U::ZERO);
198 assert_eq!(value.log(U::from(b)), e);
199 }
201 });
202 });
203 }
204
205 #[test]
206 fn test_log_pow() {
207 const_for!(BITS in NON_ZERO if (BITS >= 64) {
208 const LIMBS: usize = nlimbs(BITS);
209 type U = Uint<BITS, LIMBS>;
210 proptest!(|(b in 2_u64..100, n: U)| {
211 prop_assume!(n > U::ZERO);
212 let e = n.log(U::from(b));
213 assert!(U::from(b).pow(U::from(e)) <= n);
214 if let Some(value) = U::from(b).checked_pow(U::from(e + 1)) {
215 assert!(value > n);
216 }
217 });
218 });
219 }
220}