oxinum_float/
elementary.rs1use crate::{DBig, OxiNumError, OxiNumResult};
7use dashu_float::round::mode::HalfEven;
8use std::str::FromStr;
9
10pub fn exp(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
23 if precision == 0 {
24 return Err(OxiNumError::Precision("precision must be > 0".into()));
25 }
26 let zero = DBig::from_str("0.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
28 if *x == zero {
29 return DBig::from_str("1.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()));
30 }
31 let guard_bits = precision * 4 + 20;
32 let fbig = convert_dbig_to_fbig(x, guard_bits);
33 let result = fbig.exp();
34 let dbig = fbig_to_dbig(&result, precision);
35 Ok(truncate_to_precision(dbig, precision))
36}
37
38pub fn ln(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
55 if precision == 0 {
56 return Err(OxiNumError::Precision("precision must be > 0".into()));
57 }
58 let zero = DBig::from_str("0.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
60 if *x <= zero {
61 return Err(OxiNumError::Precision("ln(x) requires x > 0".into()));
62 }
63 let one = DBig::from_str("1.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
65 if *x == one {
66 return DBig::from_str("0.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()));
67 }
68 let guard_bits = precision * 4 + 20;
69 let fbig = convert_dbig_to_fbig(x, guard_bits);
70 let result = fbig.ln();
71 let dbig = fbig_to_dbig(&result, precision);
72 Ok(truncate_to_precision(dbig, precision))
73}
74
75pub fn sqrt(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
92 if precision == 0 {
93 return Err(OxiNumError::Precision("precision must be > 0".into()));
94 }
95 let zero = DBig::from_str("0.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
96 if *x < zero {
97 return Err(OxiNumError::Precision("sqrt(x) requires x >= 0".into()));
98 }
99 if *x == zero {
100 return Ok(zero);
101 }
102 let guard_bits = precision * 4 + 20;
103 let fbig = convert_dbig_to_fbig(x, guard_bits);
104 let result = dashu_base::SquareRoot::sqrt(&fbig);
105 let dbig = fbig_to_dbig(&result, precision);
106 Ok(truncate_to_precision(dbig, precision))
107}
108
109pub fn pow(base: &DBig, exponent: &DBig, precision: usize) -> OxiNumResult<DBig> {
129 if precision == 0 {
130 return Err(OxiNumError::Precision("precision must be > 0".into()));
131 }
132 let zero = DBig::from_str("0.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()))?;
133 if *exponent == zero {
134 return DBig::from_str("1.0").map_err(|e| OxiNumError::Parse(format!("{e}").into()));
135 }
136 if *base <= zero {
137 return Err(OxiNumError::Precision(
138 "pow(base, exp) requires base > 0".into(),
139 ));
140 }
141 let guard_bits = precision * 4 + 20;
142 let fbig_base = convert_dbig_to_fbig(base, guard_bits);
143 let fbig_exp = convert_dbig_to_fbig(exponent, guard_bits);
144 let result = fbig_base.powf(&fbig_exp);
145 let dbig = fbig_to_dbig(&result, precision);
146 Ok(truncate_to_precision(dbig, precision))
147}
148
149pub(crate) fn convert_dbig_to_fbig(
159 value: &DBig,
160 binary_precision: usize,
161) -> dashu_float::FBig<HalfEven, 2> {
162 let ctx = dashu_float::Context::<HalfEven>::new(binary_precision);
166 let repr = value
167 .clone()
168 .with_rounding::<HalfEven>()
169 .with_base_and_precision::<2>(binary_precision.max(10))
170 .value();
171 let result_repr = repr.repr().clone();
173 dashu_float::FBig::from_repr(result_repr, ctx)
174}
175
176pub(crate) fn fbig_to_dbig(
181 fbig: &dashu_float::FBig<HalfEven, 2>,
182 decimal_precision: usize,
183) -> DBig {
184 if fbig.digits() == 0 {
186 return DBig::from_str("0.0").expect("valid literal");
188 }
189 let decimal_digits = decimal_precision.max(5);
192 fbig.clone()
193 .with_base_and_precision::<10>(decimal_digits)
194 .value()
195 .with_rounding::<dashu_float::round::mode::HalfAway>()
196}
197
198pub(crate) fn truncate_to_precision(value: DBig, precision: usize) -> DBig {
200 let s = value.to_string();
201 let truncated = truncate_decimal_str(&s, precision);
202 DBig::from_str(&truncated).unwrap_or(value)
203}
204
205pub(crate) fn truncate_decimal_str(src: &str, sig_digits: usize) -> String {
212 let mut result = String::with_capacity(sig_digits + 10);
213 let mut sig_count = 0;
214
215 let trimmed = src.trim_start_matches('-');
217 let integer_is_zero = trimmed.starts_with("0.") || trimmed == "0";
218
219 let mut seen_nonzero = !integer_is_zero;
221
222 for ch in src.chars() {
223 if ch == '-' {
224 result.push(ch);
225 continue;
226 }
227 if ch == '.' {
228 result.push(ch);
229 continue;
230 }
231 if ch == 'e' || ch == 'E' {
233 break;
234 }
235 if !ch.is_ascii_digit() {
236 continue;
237 }
238
239 if !seen_nonzero && ch == '0' {
240 result.push(ch);
242 continue;
243 }
244 seen_nonzero = true;
246 sig_count += 1;
247 result.push(ch);
248 if sig_count >= sig_digits {
249 break;
250 }
251 }
252
253 let content = result.trim_start_matches('-');
255 if content.is_empty() {
256 if result.starts_with('-') {
257 return "-0".to_string();
258 }
259 return "0".to_string();
260 }
261
262 result
263}
264
265#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn exp_of_zero() {
275 let x = DBig::from_str("0.0").expect("ok");
276 let result = exp(&x, 30).expect("ok");
277 let s = result.to_string();
278 assert!(s.starts_with("1.0000") || s == "1", "exp(0) = {s}");
279 }
280
281 #[test]
282 fn exp_of_one() {
283 let x = DBig::from_str("1.0").expect("ok");
284 let result = exp(&x, 30).expect("ok");
285 let s = result.to_string();
286 assert!(s.starts_with("2.71828"), "exp(1) = {s}");
287 }
288
289 #[test]
290 fn ln_of_one() {
291 let x = DBig::from_str("1.0").expect("ok");
292 let result = ln(&x, 30).expect("ok");
293 let s = result.to_string();
294 let s_clean = s.trim_start_matches('-');
296 assert!(
297 s_clean.starts_with("0") && !s_clean.starts_with("0.1"),
298 "ln(1) = {s}"
299 );
300 }
301
302 #[test]
303 fn ln_negative_errors() {
304 let x = DBig::from_str("-1.0").expect("ok");
305 assert!(ln(&x, 30).is_err());
306 }
307
308 #[test]
309 fn sqrt_of_four() {
310 let x = DBig::from_str("4.0").expect("ok");
311 let result = sqrt(&x, 30).expect("ok");
312 let s = result.to_string();
313 assert!(s.starts_with("2.0000") || s == "2", "sqrt(4) = {s}");
314 }
315
316 #[test]
317 fn sqrt_of_two() {
318 let x = DBig::from_str("2.0").expect("ok");
319 let result = sqrt(&x, 30).expect("ok");
320 let s = result.to_string();
321 assert!(s.starts_with("1.4142135"), "sqrt(2) = {s}");
322 }
323
324 #[test]
325 fn sqrt_negative_errors() {
326 let x = DBig::from_str("-1.0").expect("ok");
327 assert!(sqrt(&x, 30).is_err());
328 }
329
330 #[test]
331 fn sqrt_of_zero() {
332 let x = DBig::from_str("0.0").expect("ok");
333 let result = sqrt(&x, 30).expect("ok");
334 let s = result.to_string();
335 assert!(s.starts_with("0"), "sqrt(0) = {s}");
336 }
337
338 #[test]
339 fn pow_two_to_ten() {
340 let base = DBig::from_str("2.0").expect("ok");
341 let exponent = DBig::from_str("10.0").expect("ok");
342 let result = pow(&base, &exponent, 20).expect("ok");
343 let s = result.to_string();
344 assert!(s.starts_with("1024"), "2^10 = {s}");
345 }
346
347 #[test]
348 fn pow_zero_exponent() {
349 let base = DBig::from_str("5.0").expect("ok");
350 let exponent = DBig::from_str("0.0").expect("ok");
351 let result = pow(&base, &exponent, 20).expect("ok");
352 let s = result.to_string();
353 assert!(s.starts_with("1"), "5^0 = {s}");
354 }
355
356 #[test]
357 fn precision_zero_errors() {
358 let x = DBig::from_str("1.0").expect("ok");
359 assert!(exp(&x, 0).is_err());
360 assert!(ln(&x, 0).is_err());
361 assert!(sqrt(&x, 0).is_err());
362 assert!(pow(&x, &x, 0).is_err());
363 }
364
365 #[test]
366 fn truncate_leading_zeros() {
367 let s = truncate_decimal_str("0.00123456789", 5);
368 assert_eq!(s, "0.0012345");
369 }
370
371 #[test]
372 fn truncate_integer_part() {
373 let s = truncate_decimal_str("123.456789", 6);
374 assert_eq!(s, "123.456");
375 }
376
377 #[test]
378 fn truncate_negative() {
379 let s = truncate_decimal_str("-3.14159", 4);
380 assert_eq!(s, "-3.141");
381 }
382}