#![deny(rustdoc::broken_intra_doc_links)]

//! This module provides functionality for computing logarithms of real and complex numbers.
//!
//! The module includes traits and implementations for computing various types of logarithms,
//! including the natural logarithm (base `e`), base-10 logarithm, and base-2 logarithm. It also
//! defines error types that can occur during the computation of logarithms.
//!
//! # Traits
//!
//! - [`Ln`]: Trait for computing the natural logarithm (base `e`) of a number.
//! - [`Log10`]: Trait for computing the base-10 logarithm of a number.
//! - [`Log2`]: Trait for computing the base-2 logarithm of a number.
//!
//! # Error Types
//!
//! - [`LogarithmRealInputErrors`]: Errors that can occur when computing the logarithm of a real number.
//! - [`LogarithmComplexInputErrors`]: Errors that can occur when computing the logarithm of a complex number.
//! - [`LogarithmRealErrors`]: Errors that can occur when computing the logarithm of a real number, including both input and output errors.
//! - [`LogarithmComplexErrors`]: Errors that can occur when computing the logarithm of a complex number, including both input and output errors.
//!
//! # Implementations
//!
//! The traits are implemented for various numeric types, including:
//!
//! - [`f64`]: Provides logarithm functions for 64-bit floating-point numbers.
//! - [`Complex<f64>`]: Provides logarithm functions for complex numbers with 64-bit floating-point components.
//! - [`RealValidated`](crate::RealValidated): Provides logarithm functions for validated real numbers.
//! - [`ComplexValidated`](crate::ComplexValidated): Provides logarithm functions for validated complex numbers.
//!
//! # Example
//!
//! ```rust
//! use num_valid::{functions::Ln, Native64};
//!
//!
//! let value = 2.718281828459045;
//! match value.try_ln() {
//!     Ok(result) => println!("Natural Logarithm: {}", result),
//!     Err(e) => println!("Error: {:?}", e),
//! }
//!
//! // Using the panicking method (simpler, panics on invalid input)
//! let result = Ln::ln(value);
//! println!("Natural Logarithm: {}", result);
//! ```

use crate::{
    functions::FunctionErrors,
    kernels::{RawComplexTrait, RawRealTrait, RawScalarTrait},
    validation::{StrictFinitePolicy, capture_backtrace},
};
use duplicate::duplicate_item;
use num::Complex;
use num::Zero;
use std::backtrace::Backtrace;
use thiserror::Error;
use try_create::ValidationPolicy;

//------------------------------------------------------------------
/// Errors that can occur when computing the logarithm of a real number.
///
/// This enum represents the possible errors that can occur when computing the logarithm of a real number.
/// It includes errors for invalid arguments and other validation errors.
///
/// # Type Parameters
///
/// - `RawReal`: A type that implements the [`RawRealTrait`] trait. This type parameter is used to specify
///   the numeric type for the computation and its associated raw error type `<RawReal as RawScalarTrait>::ValidationErrors`.
///
/// # Variants
///
/// - `NegativeArgument`: Indicates that the argument of the function is negative, which is not allowed
///   for real logarithms. This variant includes the value that is out of the domain and a backtrace.
/// - `ZeroArgument`: Indicates that the argument of the function is zero, which is not allowed for real logarithms.
///   This variant includes a backtrace.
/// - `InvalidArgument`: Indicates that the argument of the function is invalid. This variant includes
///   the source error that occurred during validation.
#[derive(Debug, Error)]
pub enum LogarithmRealInputErrors<RawReal: RawRealTrait> {
    /// The argument of the function is negative.
    ///
    /// This variant indicates that the argument of the function is negative, which is not allowed
    /// for real logarithms. It includes the value that is out of the domain and a backtrace.
    #[error("negative argument ({value})!")]
    NegativeArgument {
        /// The value that is out of the domain.
        value: RawReal,

        backtrace: Backtrace,
    },

    /// The argument of the function is zero.
    ///
    /// This variant indicates that the argument of the function is zero, which is not allowed
    /// for real logarithms. It includes a backtrace.
    #[error("zero argument!")]
    ZeroArgument { backtrace: Backtrace },

    /// The argument of the function is invalid.
    ///
    /// This variant indicates that the argument of the function is invalid. It includes the source
    /// error that occurred during validation.
    #[error("the argument is invalid!")]
    InvalidArgument {
        /// The source error that occurred during validation.
        #[source]
        #[backtrace]
        source: <RawReal as RawScalarTrait>::ValidationErrors,
    },
}

/// Errors that can occur when computing the logarithm of a complex number.
///
/// This enum represents the possible errors that can occur when computing the logarithm of a complex number.
/// It includes errors for invalid arguments and other validation errors.
///
/// # Type Parameters
///
/// - `RawComplex`: A type that implements the [`RawComplexTrait`] trait. This type parameter is used to specify
///   the numeric type for the computation and its associated raw error type `<RawComplex as RawScalarTrait>::ValidationErrors`.
///
/// # Variants
///
/// - `ZeroArgument`: Indicates that the argument of the function is zero, which is not allowed for complex logarithms.
///   This variant includes a backtrace.
/// - `InvalidArgument`: Indicates that the argument of the function is invalid. This variant includes
///   the source error that occurred during validation.
#[derive(Debug, Error)]
pub enum LogarithmComplexInputErrors<RawComplex: RawComplexTrait> {
    /// The argument of the function is zero.
    ///
    /// This variant indicates that the argument of the function is zero, which is not allowed
    /// for complex logarithms. It includes a backtrace.
    #[error("the argument is zero!")]
    ZeroArgument { backtrace: Backtrace },

    /// The argument of the function is invalid.
    ///
    /// This variant indicates that the argument of the function is invalid. It includes the source
    /// error that occurred during validation.
    #[error("the argument is invalid!")]
    InvalidArgument {
        /// The source error that occurred during validation.
        #[source]
        #[backtrace]
        source: <RawComplex as RawScalarTrait>::ValidationErrors,
    },
}

/// Errors that can occur when computing the logarithm of a real number.
///
/// This enum represents the possible errors that can occur when computing the logarithm of a real number.
/// It includes errors for invalid input values and other validation errors.
///
/// # Type Parameters
///
/// - `RawReal`: A type that implements the [`RawRealTrait`] trait. This type parameter is used to specify
///   the numeric type for the computation and its associated raw error type `<RawReal as RawScalarTrait>::ValidationErrors`.
///
/// # Variants
///
/// - `Input`: Indicates that the input value is invalid. This variant includes the source error that
///   occurred during validation.
/// - `Output`: Indicates that the output value is invalid. This variant includes the source error that
///   occurred during validation.
pub type LogarithmRealErrors<RawReal> = FunctionErrors<
    LogarithmRealInputErrors<RawReal>,
    <RawReal as RawScalarTrait>::ValidationErrors,
>;

/// Errors that can occur when computing the logarithm of a complex number.
///
/// This enum represents the possible errors that can occur when computing the logarithm of a complex number.
/// It includes errors for invalid input values and other validation errors.
///
/// # Type Parameters
///
/// - `RawComplex`: A type that implements the [`RawComplexTrait`] trait. This type parameter is used to specify
///   the numeric type for the computation and its associated raw error type `<RawComplex as RawScalarTrait>::ValidationErrors`.
///
/// # Variants
///
/// - `Input`: Indicates that the input value is invalid. This variant includes the source error that
///   occurred during validation.
/// - `Output`: Indicates that the output value is invalid. This variant includes the source error that
///   occurred during validation.
pub type LogarithmComplexErrors<RawComplex> = FunctionErrors<
    LogarithmComplexInputErrors<RawComplex>,
    <RawComplex as RawScalarTrait>::ValidationErrors,
>;

#[duplicate_item(
    trait_name try_func    func    trait_doc try_func_doc func_doc err_doc;
    [Ln]       [try_ln]    [ln]    ["Trait for computing the [*natural logarithm*](https://en.wikipedia.org/wiki/Natural_logarithm) (base `e`) of a number.\n\nProvides both a fallible method that returns a [`Result`] and a panicking method that directly returns the computed value or panics on invalid input.\n\n# Associated Types\n\n- `Error`: The error type that is returned by the `try_ln` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n- `try_ln`: Computes the *natural logarithm* of `self` and returns a [`Result`]. If the computation is successful, it returns [`Ok`] with the computed value. If an error occurs, it returns [`Err`] with the associated error.\n- `ln`: Computes the *natural logarithm* of `self` and directly returns the computed value. This method may panic (in Debug mode) if the computation fails."] ["Computes the *natural logarithm* of `self` and returns a [`Result`].\n\nIf the computation is successful, it returns [`Ok`] with the computed value. If an error occurs, it returns [`Err`] with the associated error.\n\n# Errors\n\nThis method returns an error if the computation fails. The error type is defined by the associated [`Ln::Error`] type."]    ["Computes the *natural logarithm* of `self` and directly returns the computed value.\n\nThis method may panic (in Debug mode) if the computation fails.\n\n# Panics\n\nThis method may panic (in Debug mode) if the computation fails. It is recommended to use the `try_ln` method for safe computations."]    ["The error type that is returned by the `try_ln` method."];
    [Log10]    [try_log10] [log10] ["Trait for computing the [*base-10 logarithm*](https://en.wikipedia.org/wiki/Common_logarithm) of a number.\n\nProvides both a fallible method that returns a [`Result`] and a panicking method that directly returns the computed value or panics on invalid input.\n\n# Associated Types\n\n- `Error`: The error type that is returned by the `try_log10` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n- `try_log10`: Computes the *base-10 logarithm* of `self` and returns a [`Result`]. If the computation is successful, it returns [`Ok`] with the computed value. If an error occurs, it returns [`Err`] with the associated error.\n- `log10`: Computes the *base-10 logarithm* of `self` and directly returns the computed value. This method may panic (in Debug mode) if the computation fails."]    ["Computes the *base-10 logarithm* of `self` and returns a [`Result`].\n\nIf the computation is successful, it returns [`Ok`] with the computed value. If an error occurs, it returns [`Err`] with the associated error.\n\n# Errors\n\nThis method returns an error if the computation fails. The error type is defined by the associated [`Log10::Error`] type."] ["Computes the *base-10 logarithm* of `self` and directly returns the computed value.\n\nThis method may panic (in Debug mode) if the computation fails.\n\n# Panics\n\nThis method may panic (in Debug mode) if the computation fails. It is recommended to use the `try_log10` method for safe computations."] ["The error type that is returned by the `try_log10` method."];
    [Log2]     [try_log2]  [log2]  ["Trait for computing the [*base-2 logarithm*](https://en.wikipedia.org/wiki/Binary_logarithm) of a number.\n\nProvides both a fallible method that returns a [`Result`] and a panicking method that directly returns the computed value or panics on invalid input.\n\n# Associated Types\n\n- `Error`: The error type that is returned by the `try_log2` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n- `try_log2`: Computes the *base-2 logarithm* of `self` and returns a [`Result`]. If the computation is successful, it returns [`Ok`] with the computed value. If an error occurs, it returns [`Err`] with the associated error.\n- `log2`: Computes the *base-2 logarithm* of `self` and directly returns the computed value. This method may panic (in Debug mode) if the computation fails."]          ["Computes the *base-2 logarithm* of `self` and returns a [`Result`].\n\nIf the computation is successful, it returns [`Ok`] with the computed value. If an error occurs, it returns [`Err`] with the associated error.\n\n# Errors\n\nThis method returns an error if the computation fails. The error type is defined by the associated [`Log2::Error`] type."]   ["Computes the *base-2 logarithm* of `self` and directly returns the computed value.\n\nThis method may panic (in Debug mode) if the computation fails.\n\n# Panics\n\nThis method may panic (in Debug mode) if the computation fails. It is recommended to use the `try_log2` method for safe computations."]   ["The error type that is returned by the `try_log2` method."];
)]
#[doc = trait_doc]
pub trait trait_name: Sized {
    #[doc = err_doc]
    type Error: std::error::Error;

    #[doc = try_func_doc]
    fn try_func(self) -> Result<Self, Self::Error>;

    #[doc = func_doc]
    fn func(self) -> Self;
}

#[duplicate_item(
    trait_name try_func func;
    [Ln]       [try_ln]    [ln];
    [Log10]    [try_log10] [log10];
    [Log2]     [try_log2]  [log2];
)]
impl trait_name for f64 {
    type Error = LogarithmRealErrors<Self>;

    #[inline(always)]
    fn try_func(self) -> Result<Self, Self::Error> {
        StrictFinitePolicy::<Self, 53>::validate(self)
            .map_err(|e| LogarithmRealInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                if v < 0.0 {
                    Err(LogarithmRealInputErrors::NegativeArgument {
                        value: v,
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else if v == 0. {
                    Err(LogarithmRealInputErrors::ZeroArgument {
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<Self, 53>::validate(f64::func(v))
                        .map_err(|e| LogarithmRealErrors::Output { source: e })
                }
            })
    }

    #[inline(always)]
    fn func(self) -> Self {
        #[cfg(debug_assertions)]
        {
            self.try_func().unwrap()
        }
        #[cfg(not(debug_assertions))]
        {
            f64::func(self)
        }
    }
}

#[duplicate_item(
    trait_name try_func func;
    [Ln]       [try_ln]    [ln];
    [Log10]    [try_log10] [log10];
    [Log2]     [try_log2]  [log2];
)]
impl trait_name for Complex<f64> {
    type Error = LogarithmComplexErrors<Self>;

    #[inline(always)]
    fn try_func(self) -> Result<Self, Self::Error> {
        StrictFinitePolicy::<Self, 53>::validate(self)
            .map_err(|e| LogarithmComplexInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                if Zero::is_zero(&v) {
                    Err(LogarithmComplexInputErrors::ZeroArgument {
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<Self, 53>::validate(Complex::func(v))
                        .map_err(|e| LogarithmComplexErrors::Output { source: e })
                }
            })
    }

    #[inline(always)]
    fn func(self) -> Self {
        #[cfg(debug_assertions)]
        {
            self.try_func().unwrap()
        }
        #[cfg(not(debug_assertions))]
        {
            Complex::func(self)
        }
    }
}
//------------------------------------------------------------------

//------------------------------------------------------------------
/// A convenience trait that aggregates the standard logarithm functions.
///
/// This trait serves as a shorthand for requiring a type to implement all the fundamental
/// logarithm operations:
/// - [`Ln`] (natural logarithm)
/// - [`Log10`] (base-10 logarithm)
/// - [`Log2`] (base-2 logarithm)
///
/// It is primarily used as a super-trait for [`FpScalar`](crate::FpScalar) to simplify trait bounds
/// and ensure that any scalar type in the library provides a comprehensive set of logarithmic
/// capabilities. By using `LogarithmFunctions` as a bound, you can write generic functions that
/// utilize any of its constituent trait methods.
///
/// # Examples
///
/// ```
/// use num_valid::functions::{LogarithmFunctions, Ln, Log10, Log2};
/// use std::fmt::Debug;
///
/// // A generic function that calculates various logarithms of a number.
/// // We bound T by FpScalar, which implies LogarithmFunctions.
/// fn calculate_logs<T>(x: T)
/// where
///     T: LogarithmFunctions + Clone + Debug,
/// {
///     let ln_x = x.clone().ln();
///     let log10_x = x.clone().log10();
///     let log2_x = x.log2();
///
///     println!("ln(x) = {:?}", ln_x);
///     println!("log10(x) = {:?}", log10_x);
///     println!("log2(x) = {:?}", log2_x);
/// }
///
/// let value = 100.0f64;
/// calculate_logs(value);
///
/// // Verify the results
/// assert_eq!(value.ln(), 4.605170185988092);
/// assert_eq!(value.log10(), 2.0);
/// assert_eq!(value.log2(), 6.643856189774724);
/// ```
pub trait LogarithmFunctions: Ln + Log2 + Log10 {}

#[duplicate_item(
    T;
    [f64];
    [Complex<f64>];
)]
impl LogarithmFunctions for T {}
//------------------------------------------------------------------

//------------------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(feature = "rug")]
    use crate::kernels::rug::{ComplexRugStrictFinite, RealRugStrictFinite};

    #[cfg(feature = "rug")]
    use try_create::TryNew;

    mod ln {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_ln_valid() {
                    let value = 10.0;
                    let expected_result = std::f64::consts::LN_10;
                    assert_eq!(value.ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_f64_ln_zero() {
                    let value = 0.0;
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_ln_negative() {
                    let value = -10.0;
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::NegativeArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_ln_one() {
                    let value = 1.0;
                    let expected_result = 0.0;
                    assert_eq!(value.ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_f64_ln_infinity() {
                    let value = f64::INFINITY;
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::InvalidArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_ln_nan() {
                    let value = f64::NAN;
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::InvalidArgument { .. }
                        })
                    ));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_f64_ln_valid() {
                    let value = Complex::new(10.0, 0.0);
                    let expected_result = Complex::new(std::f64::consts::LN_10, 0.0);
                    assert_eq!(value.ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_ln_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_f64_ln_negative() {
                    let value = Complex::new(-10.0, 0.0);
                    let expected_result =
                        Complex::new(std::f64::consts::LN_10, std::f64::consts::PI);
                    assert_eq!(value.ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_ln_one() {
                    let value = Complex::new(1.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_ln_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));

                    let value = Complex::new(0.0, f64::INFINITY);
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_f64_ln_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));

                    let value = Complex::new(0.0, f64::NAN);
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));
                }
            }
        }

        #[cfg(feature = "rug")]
        mod rug53 {
            use super::*;
            use rug::{Complex, Float};

            mod real {
                use super::*;

                #[test]
                fn test_real_ln_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 10.0)).unwrap();
                    let expected_result = RealRugStrictFinite::<53>::try_new(Float::with_val(
                        53,
                        std::f64::consts::LN_10,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_real_ln_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 0.0)).unwrap();
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_real_ln_negative() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, -10.0)).unwrap();
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::NegativeArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_real_ln_one() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 1.0)).unwrap();
                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_ln_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 10.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (
                            Float::with_val(53, std::f64::consts::LN_10),
                            Float::with_val(53, 0.0),
                        ),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_ln_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 0.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let result = value.try_ln();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_ln_negative() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, -10.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (
                            Float::with_val(53, std::f64::consts::LN_10),
                            Float::with_val(53, std::f64::consts::PI),
                        ),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_ln_one() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 1.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 0.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().ln(), expected_result);
                    assert_eq!(value.try_ln().unwrap(), expected_result);
                }
            }
        }
    }

    mod log10 {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_log10_valid() {
                    let value = 10.0;
                    let expected_result = 1.0;
                    assert_eq!(value.log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_f64_log10_zero() {
                    let value = 0.0;
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_log10_negative() {
                    let value = -10.0;
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::NegativeArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_log10_one() {
                    let value = 1.0;
                    let expected_result = 0.0;
                    assert_eq!(value.log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_f64_log10_infinity() {
                    let value = f64::INFINITY;
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::InvalidArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_log10_nan() {
                    let value = f64::NAN;
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::InvalidArgument { .. }
                        })
                    ));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_f64_log10_valid() {
                    let value = Complex::new(10.0, 0.0);
                    let expected_result = Complex::new(1.0, 0.0);
                    assert_eq!(value.log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_log10_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_f64_log10_negative() {
                    let value = Complex::new(-10.0, 0.0);
                    let expected_result = Complex::new(1.0, 1.3643763538418412);
                    assert_eq!(value.log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_log10_one() {
                    let value = Complex::new(1.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_log10_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));

                    let value = Complex::new(0.0, f64::INFINITY);
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_f64_log10_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));

                    let value = Complex::new(0.0, f64::NAN);
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));
                }
            }
        }

        #[cfg(feature = "rug")]
        mod rug53 {
            use super::*;
            use rug::{Complex, Float};

            mod real {
                use super::*;

                #[test]
                fn test_real_log10_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 10.0)).unwrap();
                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 1.0)).unwrap();
                    assert_eq!(value.clone().log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_real_log10_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 0.0)).unwrap();
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_real_log10_negative() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, -10.0)).unwrap();
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::NegativeArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_real_log10_one() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 1.0)).unwrap();
                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_log10_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 10.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 1.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_log10_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 0.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let result = value.try_log10();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_log10_negative() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, -10.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (
                            Float::with_val(53, 1.0),
                            Float::with_val(53, 1.3643763538418414),
                        ),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_log10_one() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 1.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 0.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log10(), expected_result);
                    assert_eq!(value.try_log10().unwrap(), expected_result);
                }
            }
        }
    }

    mod log2 {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_log2_valid() {
                    let value = 10.0;
                    let expected_result = std::f64::consts::LOG2_10;
                    assert_eq!(value.log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_f64_log2_zero() {
                    let value = 0.0;
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_log2_negative() {
                    let value = -10.0;
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::NegativeArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_log2_one() {
                    let value = 1.0;
                    let expected_result = 0.0;
                    assert_eq!(value.log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_f64_log2_infinity() {
                    let value = f64::INFINITY;
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::InvalidArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_f64_log2_nan() {
                    let value = f64::NAN;
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::InvalidArgument { .. }
                        })
                    ));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_f64_log2_valid() {
                    let value = Complex::new(10.0, 0.0);
                    let expected_result = Complex::new(3.3219280948873626, 0.0);
                    assert_eq!(value.log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_log2_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_f64_log2_negative() {
                    let value = Complex::new(-10.0, 0.0);
                    let expected_result = Complex::new(3.3219280948873626, 4.532360141827194);
                    assert_eq!(value.log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_log2_one() {
                    let value = Complex::new(1.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_f64_log2_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));

                    let value = Complex::new(0.0, f64::INFINITY);
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_f64_log2_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));

                    let value = Complex::new(0.0, f64::NAN);
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::InvalidArgument { .. }
                        })
                    ));
                }
            }
        }

        #[cfg(feature = "rug")]
        mod rug53 {
            use super::*;
            use rug::{Complex, Float};

            mod real {
                use super::*;

                #[test]
                fn test_real_log2_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 10.0)).unwrap();
                    let expected_result = RealRugStrictFinite::<53>::try_new(Float::with_val(
                        53,
                        std::f64::consts::LOG2_10,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_real_log2_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 0.0)).unwrap();
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_real_log2_negative() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, -10.0)).unwrap();
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmRealErrors::Input {
                            source: LogarithmRealInputErrors::NegativeArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_real_log2_one() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 1.0)).unwrap();
                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_log2_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 10.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (
                            Float::with_val(53, 3.3219280948873626),
                            Float::with_val(53, 0.0),
                        ),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_log2_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 0.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let result = value.try_log2();
                    assert!(matches!(
                        result,
                        Err(LogarithmComplexErrors::Input {
                            source: LogarithmComplexInputErrors::ZeroArgument { .. }
                        })
                    ));
                }

                #[test]
                fn test_complex_log2_negative() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, -10.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (
                            Float::with_val(53, 3.3219280948873626),
                            Float::with_val(53, 4.532360141827194),
                        ),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }

                #[test]
                fn test_complex_log2_one() {
                    let value = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 1.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    let expected_result = ComplexRugStrictFinite::<53>::try_new(Complex::with_val(
                        53,
                        (Float::with_val(53, 0.0), Float::with_val(53, 0.0)),
                    ))
                    .unwrap();
                    assert_eq!(value.clone().log2(), expected_result);
                    assert_eq!(value.try_log2().unwrap(), expected_result);
                }
            }
        }
    }
}
//------------------------------------------------------------------
