oxinum_float/native/
float_ln.rs1use oxinum_core::{OxiNumError, OxiNumResult, Sign};
23
24use super::constants::ln2;
25use super::float::{BigFloat, RoundingMode};
26
27impl BigFloat {
28 pub fn ln(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
43 assert!(prec > 0, "BigFloat precision must be > 0");
44
45 if self.is_nan() {
47 return Ok(BigFloat::nan(prec));
48 }
49 if self.is_infinite() {
50 return if self.sign == Sign::Negative {
51 Ok(BigFloat::nan(prec)) } else {
53 Ok(BigFloat::infinity(prec)) };
55 }
56
57 if self.is_zero() {
59 return Err(OxiNumError::Domain("ln of zero is undefined".into()));
60 }
61 if self.sign == Sign::Negative {
62 return Err(OxiNumError::Domain(
63 "ln of negative number is undefined for real BigFloat".into(),
64 ));
65 }
66
67 let one = BigFloat::from_i64(1, prec, mode);
69 if self == &one {
70 return Ok(BigFloat::zero(prec));
71 }
72
73 let prec_i = prec as i64;
78 let big_k = self.exponent.saturating_add(prec_i);
79
80 let guard = 32u32 + prec / 16 + 8;
83 let work_prec = prec.saturating_add(guard);
84
85 let m = BigFloat::from_parts(
86 Sign::Positive,
87 self.mantissa.clone(),
88 -prec_i,
89 work_prec,
90 mode,
91 );
92
93 let m_f64 = m.to_f64();
97 let ln_m_f64 = m_f64.ln(); let ln_m = newton_ln(&m, ln_m_f64, work_prec, mode)?;
100
101 let k_float = BigFloat::from_i64(big_k, work_prec, mode);
103 let ln2_val = ln2(work_prec)?;
104 let k_times_ln2 = k_float.mul_ref_with_mode(&ln2_val, mode);
105 let result = ln_m.add_ref_with_mode(&k_times_ln2, mode);
106
107 Ok(result.with_precision(prec, mode))
108 }
109}
110
111fn newton_ln(
118 m: &BigFloat,
119 ln_m_f64: f64,
120 work_prec: u32,
121 mode: RoundingMode,
122) -> OxiNumResult<BigFloat> {
123 let mut current_prec: u32 = 64;
125 let mut y = BigFloat::from_f64(ln_m_f64, current_prec)?;
126
127 let n_iters = if work_prec <= 64 {
131 3u32
132 } else {
133 let ratio = (work_prec as f64) / 53.0;
134 ratio.log2().ceil() as u32 + 3
135 };
136 let n_iters = n_iters.min(60);
137
138 for _ in 0..n_iters {
139 current_prec = (current_prec.saturating_mul(2)).min(work_prec);
141
142 let m_at_prec = m.clone().with_precision(current_prec, mode);
143 let y_at_prec = y.clone().with_precision(current_prec, mode);
144
145 let ey = y_at_prec.exp(current_prec, mode)?;
147
148 let correction = m_at_prec.div_ref_with_mode(&ey, mode)?;
150
151 let one = BigFloat::from_i64(1, current_prec, mode);
153 let diff = correction.sub_ref_with_mode(&one, mode);
154 y = y_at_prec
155 .add_ref_with_mode(&diff, mode)
156 .with_precision(current_prec, mode);
157 }
158
159 Ok(y.with_precision(work_prec, mode))
160}
161
162#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::native::{e_const, ln2 as ln2_const, RoundingMode};
170
171 fn mk(n: i64, prec: u32) -> BigFloat {
172 BigFloat::from_i64(n, prec, RoundingMode::HalfEven)
173 }
174
175 fn approx_eq_bits(a: &BigFloat, b: &BigFloat, tol_bits: u32) -> bool {
176 let diff = a.sub_ref(b).abs();
177 if diff.is_zero() {
182 return true;
183 }
184 let diff_top_exp = diff
187 .exponent
188 .saturating_add(diff.mantissa.bit_length() as i64 - 1);
189 diff_top_exp < -(tol_bits as i64)
190 }
191
192 #[test]
193 fn ln_one_is_zero() {
194 let x = mk(1, 100);
195 let result = x.ln(100, RoundingMode::HalfEven).expect("ln(1)");
196 assert!(result.is_zero(), "ln(1) should be 0, got: {result:?}");
197 }
198
199 #[test]
200 fn ln_zero_is_domain_error() {
201 let x = mk(0, 64);
202 let result = x.ln(64, RoundingMode::HalfEven);
203 assert!(
204 matches!(result, Err(OxiNumError::Domain(_))),
205 "Expected Domain error, got: {result:?}"
206 );
207 }
208
209 #[test]
210 fn ln_negative_is_domain_error() {
211 let x = mk(-1, 64);
212 let result = x.ln(64, RoundingMode::HalfEven);
213 assert!(
214 matches!(result, Err(OxiNumError::Domain(_))),
215 "Expected Domain error, got: {result:?}"
216 );
217 }
218
219 #[test]
220 fn ln_e_is_one() {
221 let prec = 100u32;
222 let e = e_const(prec).expect("e_const");
223 let result = e.ln(prec, RoundingMode::HalfEven).expect("ln(e)");
224 let one = mk(1, prec);
225 assert!(
226 approx_eq_bits(&result, &one, 85),
227 "ln(e) should ≈ 1, got: {} (diff from 1: {})",
228 result.to_f64(),
229 (result.to_f64() - 1.0).abs()
230 );
231 }
232
233 #[test]
234 fn ln2_matches_constant() {
235 let prec = 100u32;
236 let two = mk(2, prec);
237 let computed = two.ln(prec, RoundingMode::HalfEven).expect("ln(2)");
238 let expected = ln2_const(prec).expect("ln2 constant");
239 assert!(
240 approx_eq_bits(&computed, &expected, 85),
241 "ln(2) should match ln2 constant, diff = {}",
242 (computed.to_f64() - expected.to_f64()).abs()
243 );
244 }
245}