Skip to main content

oxinum_float/native/
pow.rs

1//! Exponentiation and logarithm for [`BigFloat`].
2//!
3//! # Functions provided
4//!
5//! - [`BigFloat::pow`] — `self^exp` at a specified precision. Uses repeated
6//!   squaring for exact integer exponents and `exp(exp * ln(self))` otherwise.
7//! - [`BigFloat::log`] — `log_base(self)` = `ln(self) / ln(base)`.
8//!
9//! # Design notes
10//!
11//! **Integer fast-path:** A `BigFloat` is structurally an integer when its
12//! exponent is ≥ 0 (because the mantissa is normalized: no trailing zeros,
13//! `bit_length == precision`; so every bit is above the binary point).  We
14//! detect this directly without going through `f64`, avoiding precision loss
15//! for large exponents.
16//!
17//! **Guard bits:** All intermediate computations are performed at
18//! `prec + 16` guard bits and rounded back to `prec` at the end.
19
20use oxinum_core::{OxiNumError, OxiNumResult, Sign};
21
22use super::float::{BigFloat, RoundingMode};
23
24// ---------------------------------------------------------------------------
25// pow
26// ---------------------------------------------------------------------------
27
28impl BigFloat {
29    /// Return `self^exp` at `prec` bits using the chosen rounding mode.
30    ///
31    /// # Special cases
32    ///
33    /// - `x^0 = 1` for any `x` (including `x = 0`).
34    /// - `0^pos = 0`.
35    /// - `0^neg` → [`OxiNumError::Domain`].
36    /// - Fractional exponent with non-positive base → [`OxiNumError::Domain`].
37    ///
38    /// # Errors
39    ///
40    /// - [`OxiNumError::Domain`] — domain violation (negative base with
41    ///   fractional exponent, or zero base with negative exponent).
42    /// - [`OxiNumError::Overflow`] — propagated from [`BigFloat::exp`] if the
43    ///   intermediate `exp * ln(self)` exceeds the representable range.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use oxinum_float::native::{BigFloat, RoundingMode};
49    ///
50    /// let two   = BigFloat::from_i64(2,  100, RoundingMode::HalfEven);
51    /// let ten   = BigFloat::from_i64(10, 100, RoundingMode::HalfEven);
52    /// let result = two.pow(&ten, 100, RoundingMode::HalfEven).expect("2^10");
53    /// assert!((result.to_f64() - 1024.0).abs() < 1e-10);
54    /// ```
55    pub fn pow(&self, exp: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
56        assert!(prec > 0, "BigFloat precision must be > 0");
57
58        // x^0 = 1 for ALL x (including NaN^0 = 1, Inf^0 = 1) — IEEE pow convention.
59        if exp.is_zero() {
60            return Ok(BigFloat::from_i64(1, prec, mode));
61        }
62
63        // --- IEEE-754 non-finite guards (after x^0 check) ---
64        // NaN base or NaN exponent → NaN
65        if self.is_nan() || exp.is_nan() {
66            return Ok(BigFloat::nan(prec));
67        }
68        // Inf base cases
69        if self.is_infinite() {
70            if exp.signum() > 0 {
71                // +Inf^pos = +Inf; -Inf^pos = +Inf (conservative; Wave 3 may refine for odd int)
72                return Ok(BigFloat::infinity(prec));
73            } else {
74                // +Inf^neg = +0; -Inf^neg = +0
75                return Ok(BigFloat::zero(prec));
76            }
77        }
78        // Inf exponent cases (self is finite here)
79        if exp.is_infinite() {
80            // 1^±Inf = 1 (IEEE)
81            let one = BigFloat::from_i64(1, prec, mode);
82            if self == &one {
83                return Ok(one);
84            }
85            // |base| > 1: base^(+Inf)=+Inf, base^(-Inf)=+0
86            // |base| < 1: base^(+Inf)=+0, base^(-Inf)=+Inf
87            // For non-positive base with fractional/Inf exponent, return NaN (conservative).
88            if self.signum() <= 0 {
89                return Ok(BigFloat::nan(prec));
90            }
91            let abs_f64 = self.abs().to_f64();
92            let is_gt_one = abs_f64 > 1.0;
93            let is_pos_inf = exp.sign == Sign::Positive;
94            return if is_gt_one == is_pos_inf {
95                Ok(BigFloat::infinity(prec))
96            } else {
97                Ok(BigFloat::zero(prec))
98            };
99        }
100
101        // 0^pos = 0, 0^neg = undefined
102        if self.is_zero() {
103            if exp.signum() > 0 {
104                return Ok(BigFloat::zero(prec));
105            } else {
106                return Err(OxiNumError::Domain(
107                    "0^negative_exponent is undefined".into(),
108                ));
109            }
110        }
111
112        // Detect structural integer exponent: exponent >= 0 means the
113        // BigFloat mantissa (normalized, no trailing zeros) sits fully above
114        // the binary point.  Negative sign is handled via pow_int(negative n).
115        if exp.exponent >= 0 {
116            // The exponent is an exact integer.  We may need it as i64.
117            // If it is too large to fit, fall through to the general path.
118            let exp_i64_opt = exact_integer_to_i64(exp);
119            if let Some(n) = exp_i64_opt {
120                return self.pow_int(n, prec, mode);
121            }
122            // Exponent is a huge integer — fall through to exp(n*ln(x)).
123            // (This would produce an astronomically large number, which exp()
124            // will report as Overflow.)
125        }
126
127        // General (non-integer) case: self must be strictly positive.
128        if self.signum() <= 0 {
129            return Err(OxiNumError::Domain(
130                "pow with fractional exponent requires a strictly positive base".into(),
131            ));
132        }
133
134        let guard = 16u32;
135        let work_prec = prec.saturating_add(guard);
136
137        let ln_self = self.ln(work_prec, mode)?;
138        let exp_wp = exp.clone().with_precision(work_prec, mode);
139        let product = ln_self
140            .mul_ref_with_mode(&exp_wp, mode)
141            .with_precision(work_prec, mode);
142        let result = product.exp(work_prec, mode)?;
143
144        Ok(result.with_precision(prec, mode))
145    }
146
147    /// Integer power: `self^n` via binary exponentiation (repeated squaring).
148    fn pow_int(&self, n: i64, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
149        if n == 0 {
150            return Ok(BigFloat::from_i64(1, prec, mode));
151        }
152
153        if n < 0 {
154            // self^(-|n|) = 1 / self^(|n|)
155            // Use unsigned_abs to avoid i64::MIN overflow.
156            let mag = (n as i128).unsigned_abs() as u64;
157            let positive_pow = self.pow_uint(mag, prec, mode);
158            let one = BigFloat::from_i64(1, prec, mode);
159            return one.div_ref_with_mode(&positive_pow, mode);
160        }
161
162        Ok(self.pow_uint(n as u64, prec, mode))
163    }
164
165    /// Binary exponentiation: `self^exp_u` (non-negative exponent).
166    fn pow_uint(&self, mut exp_u: u64, prec: u32, mode: RoundingMode) -> BigFloat {
167        let mut result = BigFloat::from_i64(1, prec, mode);
168        let mut base = self.clone().with_precision(prec, mode);
169
170        while exp_u > 0 {
171            if exp_u & 1 == 1 {
172                result = result
173                    .mul_ref_with_mode(&base, mode)
174                    .with_precision(prec, mode);
175            }
176            base = base
177                .mul_ref_with_mode(&base.clone(), mode)
178                .with_precision(prec, mode);
179            exp_u >>= 1;
180        }
181
182        result
183    }
184}
185
186// ---------------------------------------------------------------------------
187// log
188// ---------------------------------------------------------------------------
189
190impl BigFloat {
191    /// Return `log_base(self)` (i.e. the logarithm of `self` in the given
192    /// `base`) at `prec` bits using the chosen rounding mode.
193    ///
194    /// Computed as `ln(self) / ln(base)`.
195    ///
196    /// # Errors
197    ///
198    /// - [`OxiNumError::Domain`] — `self <= 0`, `base <= 0`, or `base == 1`.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use oxinum_float::native::{BigFloat, RoundingMode};
204    ///
205    /// let hundred = BigFloat::from_i64(100, 100, RoundingMode::HalfEven);
206    /// let ten     = BigFloat::from_i64(10,  100, RoundingMode::HalfEven);
207    /// let result  = hundred.log(&ten, 100, RoundingMode::HalfEven).expect("log_10(100)");
208    /// assert!((result.to_f64() - 2.0).abs() < 1e-14);
209    /// ```
210    pub fn log(&self, base: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
211        assert!(prec > 0, "BigFloat precision must be > 0");
212
213        if self.is_zero() || self.signum() < 0 {
214            return Err(OxiNumError::Domain(
215                "log of non-positive number is undefined".into(),
216            ));
217        }
218        if base.is_zero() || base.signum() < 0 {
219            return Err(OxiNumError::Domain(
220                "log with non-positive base is undefined".into(),
221            ));
222        }
223
224        // base == 1: log is undefined.
225        let one = BigFloat::from_i64(1, prec, mode);
226        if base == &one {
227            return Err(OxiNumError::Domain("log base 1 is undefined".into()));
228        }
229
230        let guard = 16u32;
231        let work_prec = prec.saturating_add(guard);
232
233        let ln_self = self.ln(work_prec, mode)?;
234        let ln_base = base.ln(work_prec, mode)?;
235
236        // Divide using div_ref_with_mode (Div operator panics on zero — this
237        // is unreachable because ln(1) = 0 case was caught above, but we use
238        // the Result form regardless to be safe).
239        let result = ln_self.div_ref_with_mode(&ln_base, mode)?;
240
241        Ok(result.with_precision(prec, mode))
242    }
243}
244
245// ---------------------------------------------------------------------------
246// Helper
247// ---------------------------------------------------------------------------
248
249/// If `f` represents an exact integer that fits in `i64`, return `Some(n)`.
250/// Returns `None` if the value is too large or would lose precision.
251///
252/// A normalized `BigFloat` with `exponent >= 0` represents `mantissa * 2^exp`
253/// which is always an integer.  We need to fit it in i64.
254fn exact_integer_to_i64(f: &BigFloat) -> Option<i64> {
255    // f.exponent >= 0, so value = mantissa * 2^exponent — it is an integer.
256    // The mantissa's bit_length == f.precision.
257    // Total bits in the value = bit_length + exponent (as integer shift).
258    // Must fit in 63 bits (signed) plus sign.
259    let bit_len = f.mantissa().bit_length();
260    let total_bits = bit_len.saturating_add(f.exponent() as u64);
261    if total_bits > 63 {
262        return None;
263    }
264    // safe: mantissa fits in u64 at <= 63 significant bits
265    let mantissa_u64 = f.mantissa().to_u64()?;
266    // shift: 0 <= f.exponent() (guaranteed by the caller)
267    let exp = f.exponent() as u64;
268    let value = mantissa_u64.checked_shl(exp as u32)?;
269    // Apply sign
270    if f.sign() == oxinum_core::Sign::Negative {
271        // i64::MIN is -2^63 (= 9223372036854775808).
272        // value <= 2^63-1 ensures -(value as i64) doesn't overflow.
273        if value > i64::MAX as u64 {
274            return None;
275        }
276        Some(-(value as i64))
277    } else {
278        if value > i64::MAX as u64 {
279            return None;
280        }
281        Some(value as i64)
282    }
283}
284
285// ---------------------------------------------------------------------------
286// Unit tests
287// ---------------------------------------------------------------------------
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use crate::native::RoundingMode;
293
294    const MODE: RoundingMode = RoundingMode::HalfEven;
295
296    fn mk(n: i64, prec: u32) -> BigFloat {
297        BigFloat::from_i64(n, prec, MODE)
298    }
299
300    fn flt(x: f64, prec: u32) -> BigFloat {
301        BigFloat::from_f64(x, prec).expect("from_f64")
302    }
303
304    // --- pow tests ---
305
306    #[test]
307    fn pow_two_to_ten() {
308        let two = mk(2, 100);
309        let ten = mk(10, 100);
310        let result = two.pow(&ten, 100, MODE).expect("2^10");
311        assert!(
312            (result.to_f64() - 1024.0).abs() < 1e-10,
313            "2^10 = {}",
314            result.to_f64()
315        );
316    }
317
318    #[test]
319    fn pow_zero_exponent_is_one() {
320        let zero_exp = mk(0, 64);
321        for x_f in [2.0f64, 0.5, 10.0, 0.001, -3.0] {
322            let x = flt(x_f, 64);
323            let result = x.pow(&zero_exp, 64, MODE).expect("x^0");
324            assert!(
325                (result.to_f64() - 1.0).abs() < 1e-14,
326                "{}^0 should be 1, got {}",
327                x_f,
328                result.to_f64()
329            );
330        }
331    }
332
333    #[test]
334    fn pow_zero_base_positive_exp() {
335        let z = mk(0, 64);
336        let pos = mk(3, 64);
337        let result = z.pow(&pos, 64, MODE).expect("0^3");
338        assert!(result.is_zero(), "0^3 should be 0");
339    }
340
341    #[test]
342    fn pow_zero_base_negative_exp_is_error() {
343        let z = mk(0, 64);
344        let neg = mk(-1, 64);
345        assert!(z.pow(&neg, 64, MODE).is_err());
346    }
347
348    #[test]
349    fn pow_negative_base_fractional_exp_is_error() {
350        let neg = mk(-2, 64);
351        let half = flt(1.5, 64);
352        assert!(neg.pow(&half, 64, MODE).is_err());
353    }
354
355    #[test]
356    fn pow_inverse_property() {
357        // x^y * x^(-y) ≈ 1
358        let prec = 100u32;
359        for x_f in [2.0f64, 3.0, 7.5, 0.5] {
360            let x = flt(x_f, prec);
361            let y = flt(1.5, prec);
362            let neg_y = y.neg();
363            let xy = x.pow(&y, prec, MODE).expect("x^y");
364            let xny = x.pow(&neg_y, prec, MODE).expect("x^(-y)");
365            let product = xy.mul_ref_with_mode(&xny, MODE).with_precision(prec, MODE);
366            assert!(
367                (product.to_f64() - 1.0).abs() < 1e-13,
368                "x^y * x^(-y) ≠ 1 for x={}, got {}",
369                x_f,
370                product.to_f64()
371            );
372        }
373    }
374
375    #[test]
376    fn pow_negative_integer_exponent() {
377        // 2^(-1) = 0.5
378        let two = mk(2, 100);
379        let neg_one = mk(-1, 100);
380        let result = two.pow(&neg_one, 100, MODE).expect("2^(-1)");
381        assert!(
382            (result.to_f64() - 0.5).abs() < 1e-14,
383            "2^-1 = {}",
384            result.to_f64()
385        );
386    }
387
388    #[test]
389    fn pow_large_integer_exponent_cross_val() {
390        // 3^20 = 3486784401
391        let three = mk(3, 100);
392        let twenty = mk(20, 100);
393        let result = three.pow(&twenty, 100, MODE).expect("3^20");
394        assert!(
395            (result.to_f64() - 3_486_784_401.0_f64).abs() < 1.0,
396            "3^20 ≈ {}, expected 3486784401",
397            result.to_f64()
398        );
399    }
400
401    #[test]
402    fn pow_fractional_exp_sqrt() {
403        // x^0.5 ≈ sqrt(x)
404        let prec = 100u32;
405        for x_f in [4.0f64, 9.0, 2.0, 0.25] {
406            let x = flt(x_f, prec);
407            let half = flt(0.5, prec);
408            let result = x.pow(&half, prec, MODE).expect("x^0.5");
409            let expected = x_f.sqrt();
410            assert!(
411                (result.to_f64() - expected).abs() < 1e-13,
412                "{}^0.5 ≈ {}, expected {}",
413                x_f,
414                result.to_f64(),
415                expected
416            );
417        }
418    }
419
420    // --- log tests ---
421
422    #[test]
423    fn log_base_10_of_100() {
424        let hundred = mk(100, 100);
425        let ten = mk(10, 100);
426        let result = hundred.log(&ten, 100, MODE).expect("log_10(100)");
427        assert!(
428            (result.to_f64() - 2.0).abs() < 1e-14,
429            "log_10(100) = {}, expected 2",
430            result.to_f64()
431        );
432    }
433
434    #[test]
435    fn log_base_x_of_x_is_one() {
436        let prec = 100u32;
437        for x_f in [2.0f64, std::f64::consts::E, 10.0, 100.0] {
438            let x = flt(x_f, prec);
439            let result = x.log(&x.clone(), prec, MODE).expect("log_x(x)");
440            assert!(
441                (result.to_f64() - 1.0).abs() < 1e-12,
442                "log_{}({}) ≠ 1, got {}",
443                x_f,
444                x_f,
445                result.to_f64()
446            );
447        }
448    }
449
450    #[test]
451    fn log_base_10_of_1000() {
452        let val = mk(1000, 100);
453        let ten = mk(10, 100);
454        let result = val.log(&ten, 100, MODE).expect("log_10(1000)");
455        assert!(
456            (result.to_f64() - 3.0).abs() < 1e-14,
457            "log_10(1000) = {}, expected 3",
458            result.to_f64()
459        );
460    }
461
462    #[test]
463    fn log_base_non_positive_is_error() {
464        let pos = mk(8, 64);
465        let zero_base = mk(0, 64);
466        let neg_base = mk(-2, 64);
467        assert!(pos.log(&zero_base, 64, MODE).is_err());
468        assert!(pos.log(&neg_base, 64, MODE).is_err());
469    }
470
471    #[test]
472    fn log_of_non_positive_is_error() {
473        let ten = mk(10, 64);
474        let zero_val = mk(0, 64);
475        let neg_val = mk(-1, 64);
476        assert!(zero_val.log(&ten, 64, MODE).is_err());
477        assert!(neg_val.log(&ten, 64, MODE).is_err());
478    }
479
480    #[test]
481    fn log_base_1_is_error() {
482        let val = mk(5, 64);
483        let one = mk(1, 64);
484        assert!(val.log(&one, 64, MODE).is_err());
485    }
486}