use oxinum_core::{OxiNumError, OxiNumResult, Sign};
use super::float::{BigFloat, RoundingMode};
impl BigFloat {
pub fn pow(&self, exp: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
assert!(prec > 0, "BigFloat precision must be > 0");
if exp.is_zero() {
return Ok(BigFloat::from_i64(1, prec, mode));
}
if self.is_nan() || exp.is_nan() {
return Ok(BigFloat::nan(prec));
}
if self.is_infinite() {
if exp.signum() > 0 {
return Ok(BigFloat::infinity(prec));
} else {
return Ok(BigFloat::zero(prec));
}
}
if exp.is_infinite() {
let one = BigFloat::from_i64(1, prec, mode);
if self == &one {
return Ok(one);
}
if self.signum() <= 0 {
return Ok(BigFloat::nan(prec));
}
let abs_f64 = self.abs().to_f64();
let is_gt_one = abs_f64 > 1.0;
let is_pos_inf = exp.sign == Sign::Positive;
return if is_gt_one == is_pos_inf {
Ok(BigFloat::infinity(prec))
} else {
Ok(BigFloat::zero(prec))
};
}
if self.is_zero() {
if exp.signum() > 0 {
return Ok(BigFloat::zero(prec));
} else {
return Err(OxiNumError::Domain(
"0^negative_exponent is undefined".into(),
));
}
}
if exp.exponent >= 0 {
let exp_i64_opt = exact_integer_to_i64(exp);
if let Some(n) = exp_i64_opt {
return self.pow_int(n, prec, mode);
}
}
if self.signum() <= 0 {
return Err(OxiNumError::Domain(
"pow with fractional exponent requires a strictly positive base".into(),
));
}
let guard = 16u32;
let work_prec = prec.saturating_add(guard);
let ln_self = self.ln(work_prec, mode)?;
let exp_wp = exp.clone().with_precision(work_prec, mode);
let product = ln_self
.mul_ref_with_mode(&exp_wp, mode)
.with_precision(work_prec, mode);
let result = product.exp(work_prec, mode)?;
Ok(result.with_precision(prec, mode))
}
fn pow_int(&self, n: i64, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
if n == 0 {
return Ok(BigFloat::from_i64(1, prec, mode));
}
if n < 0 {
let mag = (n as i128).unsigned_abs() as u64;
let positive_pow = self.pow_uint(mag, prec, mode);
let one = BigFloat::from_i64(1, prec, mode);
return one.div_ref_with_mode(&positive_pow, mode);
}
Ok(self.pow_uint(n as u64, prec, mode))
}
fn pow_uint(&self, mut exp_u: u64, prec: u32, mode: RoundingMode) -> BigFloat {
let mut result = BigFloat::from_i64(1, prec, mode);
let mut base = self.clone().with_precision(prec, mode);
while exp_u > 0 {
if exp_u & 1 == 1 {
result = result
.mul_ref_with_mode(&base, mode)
.with_precision(prec, mode);
}
base = base
.mul_ref_with_mode(&base.clone(), mode)
.with_precision(prec, mode);
exp_u >>= 1;
}
result
}
}
impl BigFloat {
pub fn log(&self, base: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
assert!(prec > 0, "BigFloat precision must be > 0");
if self.is_zero() || self.signum() < 0 {
return Err(OxiNumError::Domain(
"log of non-positive number is undefined".into(),
));
}
if base.is_zero() || base.signum() < 0 {
return Err(OxiNumError::Domain(
"log with non-positive base is undefined".into(),
));
}
let one = BigFloat::from_i64(1, prec, mode);
if base == &one {
return Err(OxiNumError::Domain("log base 1 is undefined".into()));
}
let guard = 16u32;
let work_prec = prec.saturating_add(guard);
let ln_self = self.ln(work_prec, mode)?;
let ln_base = base.ln(work_prec, mode)?;
let result = ln_self.div_ref_with_mode(&ln_base, mode)?;
Ok(result.with_precision(prec, mode))
}
}
fn exact_integer_to_i64(f: &BigFloat) -> Option<i64> {
let bit_len = f.mantissa().bit_length();
let total_bits = bit_len.saturating_add(f.exponent() as u64);
if total_bits > 63 {
return None;
}
let mantissa_u64 = f.mantissa().to_u64()?;
let exp = f.exponent() as u64;
let value = mantissa_u64.checked_shl(exp as u32)?;
if f.sign() == oxinum_core::Sign::Negative {
if value > i64::MAX as u64 {
return None;
}
Some(-(value as i64))
} else {
if value > i64::MAX as u64 {
return None;
}
Some(value as i64)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::native::RoundingMode;
const MODE: RoundingMode = RoundingMode::HalfEven;
fn mk(n: i64, prec: u32) -> BigFloat {
BigFloat::from_i64(n, prec, MODE)
}
fn flt(x: f64, prec: u32) -> BigFloat {
BigFloat::from_f64(x, prec).expect("from_f64")
}
#[test]
fn pow_two_to_ten() {
let two = mk(2, 100);
let ten = mk(10, 100);
let result = two.pow(&ten, 100, MODE).expect("2^10");
assert!(
(result.to_f64() - 1024.0).abs() < 1e-10,
"2^10 = {}",
result.to_f64()
);
}
#[test]
fn pow_zero_exponent_is_one() {
let zero_exp = mk(0, 64);
for x_f in [2.0f64, 0.5, 10.0, 0.001, -3.0] {
let x = flt(x_f, 64);
let result = x.pow(&zero_exp, 64, MODE).expect("x^0");
assert!(
(result.to_f64() - 1.0).abs() < 1e-14,
"{}^0 should be 1, got {}",
x_f,
result.to_f64()
);
}
}
#[test]
fn pow_zero_base_positive_exp() {
let z = mk(0, 64);
let pos = mk(3, 64);
let result = z.pow(&pos, 64, MODE).expect("0^3");
assert!(result.is_zero(), "0^3 should be 0");
}
#[test]
fn pow_zero_base_negative_exp_is_error() {
let z = mk(0, 64);
let neg = mk(-1, 64);
assert!(z.pow(&neg, 64, MODE).is_err());
}
#[test]
fn pow_negative_base_fractional_exp_is_error() {
let neg = mk(-2, 64);
let half = flt(1.5, 64);
assert!(neg.pow(&half, 64, MODE).is_err());
}
#[test]
fn pow_inverse_property() {
let prec = 100u32;
for x_f in [2.0f64, 3.0, 7.5, 0.5] {
let x = flt(x_f, prec);
let y = flt(1.5, prec);
let neg_y = y.neg();
let xy = x.pow(&y, prec, MODE).expect("x^y");
let xny = x.pow(&neg_y, prec, MODE).expect("x^(-y)");
let product = xy.mul_ref_with_mode(&xny, MODE).with_precision(prec, MODE);
assert!(
(product.to_f64() - 1.0).abs() < 1e-13,
"x^y * x^(-y) ≠ 1 for x={}, got {}",
x_f,
product.to_f64()
);
}
}
#[test]
fn pow_negative_integer_exponent() {
let two = mk(2, 100);
let neg_one = mk(-1, 100);
let result = two.pow(&neg_one, 100, MODE).expect("2^(-1)");
assert!(
(result.to_f64() - 0.5).abs() < 1e-14,
"2^-1 = {}",
result.to_f64()
);
}
#[test]
fn pow_large_integer_exponent_cross_val() {
let three = mk(3, 100);
let twenty = mk(20, 100);
let result = three.pow(&twenty, 100, MODE).expect("3^20");
assert!(
(result.to_f64() - 3_486_784_401.0_f64).abs() < 1.0,
"3^20 ≈ {}, expected 3486784401",
result.to_f64()
);
}
#[test]
fn pow_fractional_exp_sqrt() {
let prec = 100u32;
for x_f in [4.0f64, 9.0, 2.0, 0.25] {
let x = flt(x_f, prec);
let half = flt(0.5, prec);
let result = x.pow(&half, prec, MODE).expect("x^0.5");
let expected = x_f.sqrt();
assert!(
(result.to_f64() - expected).abs() < 1e-13,
"{}^0.5 ≈ {}, expected {}",
x_f,
result.to_f64(),
expected
);
}
}
#[test]
fn log_base_10_of_100() {
let hundred = mk(100, 100);
let ten = mk(10, 100);
let result = hundred.log(&ten, 100, MODE).expect("log_10(100)");
assert!(
(result.to_f64() - 2.0).abs() < 1e-14,
"log_10(100) = {}, expected 2",
result.to_f64()
);
}
#[test]
fn log_base_x_of_x_is_one() {
let prec = 100u32;
for x_f in [2.0f64, std::f64::consts::E, 10.0, 100.0] {
let x = flt(x_f, prec);
let result = x.log(&x.clone(), prec, MODE).expect("log_x(x)");
assert!(
(result.to_f64() - 1.0).abs() < 1e-12,
"log_{}({}) ≠ 1, got {}",
x_f,
x_f,
result.to_f64()
);
}
}
#[test]
fn log_base_10_of_1000() {
let val = mk(1000, 100);
let ten = mk(10, 100);
let result = val.log(&ten, 100, MODE).expect("log_10(1000)");
assert!(
(result.to_f64() - 3.0).abs() < 1e-14,
"log_10(1000) = {}, expected 3",
result.to_f64()
);
}
#[test]
fn log_base_non_positive_is_error() {
let pos = mk(8, 64);
let zero_base = mk(0, 64);
let neg_base = mk(-2, 64);
assert!(pos.log(&zero_base, 64, MODE).is_err());
assert!(pos.log(&neg_base, 64, MODE).is_err());
}
#[test]
fn log_of_non_positive_is_error() {
let ten = mk(10, 64);
let zero_val = mk(0, 64);
let neg_val = mk(-1, 64);
assert!(zero_val.log(&ten, 64, MODE).is_err());
assert!(neg_val.log(&ten, 64, MODE).is_err());
}
#[test]
fn log_base_1_is_error() {
let val = mk(5, 64);
let one = mk(1, 64);
assert!(val.log(&one, 64, MODE).is_err());
}
}