#![deny(rustdoc::broken_intra_doc_links)]

//! This module provides traits, error types, and implementations for various
//! hyperbolic functions and their inverses (e.g., `sinh`, `cosh`, `asinh`, `acosh`).
//!
//! It defines a set of traits (e.g., [`SinH`], [`ACosH`], etc.) for standard
//! hyperbolic operations. These traits are implemented for [`f64`], [`num::Complex<f64>`],
//! [`RealValidated`](crate::RealValidated), [`ComplexValidated`](crate::ComplexValidated),
//! and can be extended to other numeric types satisfying the [`RealScalar`](crate::RealScalar) or
//! [`ComplexScalar`](crate::ComplexScalar) traits.
//!
//! A comprehensive error handling system is in place, utilizing specific input
//! error enums (e.g., [`ACosHInputErrors`], [`TanHComplexInputErrors`], etc.) and
//! general function error type aliases (e.g., [`SinHErrors`], [`ACosHErrors`], etc.)
//! built upon the [`FunctionErrors`] struct. This allows for granular reporting
//! of issues such as invalid arguments (NaN, infinity) or out-of-domain values.
//!
//! The design emphasizes robustness through strict validation of inputs and outputs,
//! primarily using the [`StrictFinitePolicy`].

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

//------------------------------------------------------------------------------------------------
#[duplicate_item(
    enum_name          enum_doc;
    [CosHInputErrors]  ["Errors that can occur during the computation of the hyperbolic cosine.\n\nPrimarily, this enum handles issues related to invalid input arguments,\\nsuch as NaN (Not a Number) or infinity, as determined by the\\nimplemented validation policy (e.g., [`StrictFinitePolicy`]).\n\n# Type Parameters\n\n- `ScalarType`: The numeric type for the computation (e.g., `f64`, `Complex<f64>`),\\n  which must implement the [`RawScalarTrait`] trait. The associated type\\n  `<RawScalar as RawScalarTrait>::ValidationErrors` is used to convey details of underlying\\n  validation failures.\n\n# Variants\n\n- `InvalidArgument`: Signals that the input argument to the hyperbolic cosine\\n  function is invalid. This variant wraps the specific error encountered\\n  during the validation process, providing more context on the nature of\\n  the invalidity."];
    [SinHInputErrors]  ["Errors that can occur during the computation of the hyperbolic sine.\n\nPrimarily, this enum handles issues related to invalid input arguments,\\nsuch as NaN (Not a Number) or infinity, as determined by the\\nimplemented validation policy (e.g., [`StrictFinitePolicy`]).\n\n# Type Parameters\n\n- `ScalarType`: The numeric type for the computation (e.g., `f64`, `Complex<f64>`),\\n  which must implement the [`RawScalarTrait`] trait. The associated type\\n  `<RawScalar as RawScalarTrait>::ValidationErrors` is used to convey details of underlying\\n  validation failures.\n\n# Variants\n\n- `InvalidArgument`: Signals that the input argument to the hyperbolic sine\\n  function is invalid. This variant wraps the specific error encountered\\n  during the validation process, providing more context on the nature of\\n  the invalidity."];
    [ASinHInputErrors] ["Errors that can occur during the computation of the inverse hyperbolic sine.\n\nPrimarily, this enum handles issues related to invalid input arguments,\\nsuch as NaN (Not a Number) or infinity, as determined by the\\nimplemented validation policy (e.g., [`StrictFinitePolicy`]).\\nNote: `asinh` is defined for all real and complex numbers, so domain errors\\nare not typically raised at this input stage.\n\n# Type Parameters\n\n- `ScalarType`: The numeric type for the computation (e.g., `f64`, `Complex<f64>`),\\n  which must implement the [`RawScalarTrait`] trait. The associated type\\n  `<RawScalar as RawScalarTrait>::ValidationErrors` is used to convey details of underlying\\n  validation failures.\n\n# Variants\n\n- `InvalidArgument`: Signals that the input argument to the inverse hyperbolic sine\\n  function is invalid. This variant wraps the specific error encountered\\n  during the validation process, providing more context on the nature of\\n  the invalidity."];
)]
#[derive(Debug, Error)]
#[doc = enum_doc]
pub enum enum_name<RawScalar: RawScalarTrait> {
    /// The input argument is invalid (e.g., NaN or infinity).
    ///
    /// This error is raised if the input value fails the validation
    /// (using the chosen implementation of [`ValidationPolicy`]).
    /// It contains the underlying error from the validation mechanism.
    #[error("the argument of the function is invalid!")]
    InvalidArgument {
        /// The source error that occurred during validation.
        #[source]
        #[backtrace]
        source: <RawScalar as RawScalarTrait>::ValidationErrors,
    },
}

/// Errors that can occur when computing the *hyperbolic tangent* of a *real number*.
///
/// This enum covers errors related to invalid input arguments (e.g., NaN, infinity)
/// as determined by the validation policy for real numbers.
///
/// # Type Parameters
///
/// - `RawReal`: The real numeric type (e.g., `f64`) for the computation,
///   implementing [`RawRealTrait`]. The type `<RawReal as RawScalarTrait>::ValidationErrors` is used for
///   underlying validation error details.
///
/// # Variants
///
/// - `InvalidArgument`: The input argument is invalid (e.g., NaN or infinity).
///   Wraps the source error from the validation process.
#[derive(Debug, Error)]
pub enum TanHRealInputErrors<RawReal: RawRealTrait> {
    /// The input argument is invalid (e.g., NaN or infinity).
    ///
    /// This error is raised if the input value fails the validation
    /// (using the chosen implementation of [`ValidationPolicy`]).
    /// It contains the underlying error from the validation mechanism.
    #[error("the argument of the function is invalid!")]
    InvalidArgument {
        /// The source error that occurred during validation.
        #[source]
        #[backtrace]
        source: <RawReal as RawScalarTrait>::ValidationErrors,
    },
}

#[duplicate_item(
    enum_name          enum_doc;
    [ACosHInputErrors] ["Errors that can occur during the computation of the inverse hyperbolic cosine.\n\nThis enum handles issues related to invalid input arguments, such as NaN or\\ninfinity, as determined by the validation policy (e.g., [`StrictFinitePolicy`]).\\nIt also includes domain errors specific to `acosh`, which is defined for `x >= 1`\\nfor real numbers and for all complex numbers.\n\n# Type Parameters\n\n- `RawScalar`: The numeric type (e.g., `f64`, `Complex<f64>`), implementing [`RawScalarTrait`].\\n  `<RawScalar as RawScalarTrait>::ValidationErrors` provides details of validation failures.\n\n# Variants\n\n- `InvalidArgument`: Input is invalid (e.g., NaN, infinity). Wraps `<RawScalar as RawScalarTrait>::ValidationErrors`.\\n- `DomainError`: Input is outside the function's domain (e.g., `x < 1` for real `acosh`)."];
    [ATanHInputErrors] ["Errors that can occur during the computation of the inverse hyperbolic tangent.\n\nThis enum handles issues related to invalid input arguments, such as NaN or\\ninfinity, as determined by the validation policy (e.g., [`StrictFinitePolicy`]).\\nIt also includes domain errors specific to `atanh`, which is defined for `-1 < x < 1`\\nfor real numbers and for complex numbers `z` where `z != ±1`.\n\n# Type Parameters\n\n- `RawScalar`: The numeric type (e.g., `f64`, `Complex<f64>`), implementing [`RawScalarTrait`].\\n  `<RawScalar as RawScalarTrait>::ValidationErrors` provides details of validation failures.\n\n# Variants\n\n- `InvalidArgument`: Input is invalid (e.g., NaN, infinity). Wraps `<RawScalar as RawScalarTrait>::ValidationErrors`.\\n- `DomainError`: Input is outside the function's domain (e.g., `|x| >= 1` for real `atanh`)."];
)]
#[derive(Debug, Error)]
#[doc = enum_doc]
pub enum enum_name<RawScalar: RawScalarTrait> {
    /// The input value is outside the mathematical domain of the function.
    ///
    /// For example, for real `acosh(x)`, `x` must be `>= 1`. For real `atanh(x)`, `|x|` must be `< 1`.
    /// For complex versions, this refers to values on the branch cuts if they are explicitly excluded.
    #[error("the input argument is outside the domain of the function!")]
    OutOfDomain {
        value: RawScalar,

        backtrace: Backtrace,
    },

    /// The input argument is invalid (e.g., NaN or infinity).
    ///
    /// This error is raised if the input value fails the validation
    /// (using the chosen implementation of [`ValidationPolicy`]).
    /// It contains the underlying error from the validation mechanism.
    #[error("the argument of the function is invalid!")]
    InvalidArgument {
        /// The source error that occurred during validation.
        #[source]
        #[backtrace]
        source: <RawScalar as RawScalarTrait>::ValidationErrors,
    },
}

#[duplicate_item(
    enum_name     ErrIn              enum_doc;
    [CosHErrors]  [CosHInputErrors]  ["Errors that can occur when computing the *hyperbolic cosine* of a real or complex number.\n\nIt includes errors for invalid input and output values.\n\n# Type Parameters\n\n- `RawScalar`: A type that implements the [`RawScalarTrait`] trait. This type parameter is used to specify the numeric type for the computation and its associated raw error type `<RawScalar as RawScalarTrait>::ValidationErrors`.\n\n# Variants\n\n- `Input`: Indicates that the input value is invalid. This variant includes the source error that occurred during validation.\n- `Output`: Indicates that the output value is invalid. This variant includes the source error that occurred during validation."];
    [SinHErrors]  [SinHInputErrors]  ["Errors that can occur when computing the *hyperbolic sine* of a real or complex number.\n\nIt includes errors for invalid input and output values.\n\n# Type Parameters\n\n- `RawScalar`: A type that implements the [`RawScalarTrait`] trait. This type parameter is used to specify the numeric type for the computation and its associated raw error type `<RawScalar as RawScalarTrait>::ValidationErrors`.\n\n# Variants\n\n- `Input`: Indicates that the input value is invalid. This variant includes the source error that occurred during validation.\n- `Output`: Indicates that the output value is invalid. This variant includes the source error that occurred during validation."];
    [ASinHErrors] [ASinHInputErrors] ["Errors that can occur when computing the *inverse hyperbolic sine* of a real or complex number.\n\nIt includes errors for invalid input and output values.\n\n# Type Parameters\n\n- `RawScalar`: A type that implements the [`RawScalarTrait`] trait. This type parameter is used to specify the numeric type for the computation and its associated raw error type `<RawScalar as RawScalarTrait>::ValidationErrors`.\n\n# Variants\n\n- `Input`: Indicates that the input value is invalid. This variant includes the source error that occurred during validation.\n- `Output`: Indicates that the output value is invalid. This variant includes the source error that occurred during validation."];
    [ACosHErrors] [ACosHInputErrors] ["Errors that can occur when computing the *inverse hyperbolic cosine* of a real or complex number.\n\nIt includes errors for invalid input and output values.\n\n# Type Parameters\n\n- `RawScalar`: A type that implements the [`RawScalarTrait`] trait. This type parameter is used to specify the numeric type for the computation and its associated raw error type `<RawScalar as RawScalarTrait>::ValidationErrors`.\n\n# Variants\n\n- `Input`: Indicates that the input value is invalid. This variant includes the source error that occurred during validation.\n- `Output`: Indicates that the output value is invalid. This variant includes the source error that occurred during validation."];
    [ATanHErrors] [ATanHInputErrors] ["Errors that can occur when computing the *inverse hyperbolic tangent* of a real or complex number.\n\nIt includes errors for invalid input and output values.\n\n# Type Parameters\n\n- `RawScalar`: A type that implements the [`RawScalarTrait`] trait. This type parameter is used to specify the numeric type for the computation and its associated raw error type `<RawScalar as RawScalarTrait>::ValidationErrors`.\n\n# Variants\n\n- `Input`: Indicates that the input value is invalid. This variant includes the source error that occurred during validation.\n- `Output`: Indicates that the output value is invalid. This variant includes the source error that occurred during validation."];
)]
#[doc = enum_doc]
pub type enum_name<RawScalar> =
    FunctionErrors<ErrIn<RawScalar>, <RawScalar as RawScalarTrait>::ValidationErrors>;

/// Errors that can occur when computing the *hyperbolic tangent* of a *real number*.
///
/// It includes errors for invalid input and output values.
///
/// # 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 TanHRealErrors<RawReal> =
    FunctionErrors<TanHRealInputErrors<RawReal>, <RawReal as RawScalarTrait>::ValidationErrors>;

/// Errors that can occur when computing the *hyperbolic tangent* of a *complex number*.
///
/// It includes errors for invalid arguments and other validation errors.
///
/// # Type Parameters
///
/// - `ComplexType`: A type that implements the [`RawComplexTrait`](crate::kernels::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
///
/// - `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 TanHComplexInputErrors<RawComplex: RawScalarTrait> {
    #[error("the input argument is outside the domain of the function!")]
    OutOfDomain {
        value: RawComplex,

        backtrace: Backtrace,
    },

    /// The input argument is invalid (e.g., NaN or infinity).
    ///
    /// This error is raised if the input value fails the validation
    /// (using the chosen implementation of [`ValidationPolicy`]).
    /// It contains the underlying error from the validation mechanism.
    #[error("the argument of the function is invalid!")]
    InvalidArgument {
        /// The source error that occurred during validation.
        #[source]
        #[backtrace]
        source: <RawComplex as RawScalarTrait>::ValidationErrors,
    },
}

/// Errors that can occur when computing the *hyperbolic tangent* of a *complex number*.
///
/// It includes errors for invalid input and output values.
///
/// # Type Parameters
///
/// - `RawComplex`: A type that implements the [`RawComplexTrait`](crate::kernels::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 TanHComplexErrors<RawComplex> = FunctionErrors<
    TanHComplexInputErrors<RawComplex>,
    <RawComplex as RawScalarTrait>::ValidationErrors,
>;
//------------------------------------------------------------------------------------------------

//------------------------------------------------------------------------------------------------
#[duplicate_item(
    T       try_func    func    trait_doc try_func_doc func_doc err_doc;
    [ACosH] [try_acosh] [acosh] ["Trait for computing the *inverse hyperbolic cosine* of a number.\n\nThis trait defines methods for computing the *inverse hyperbolic cosine* of a number. Provides 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_acosh` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n - `try_acosh`: Computes the *inverse hyperbolic cosine* 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\n - `acosh`: Computes the *inverse hyperbolic cosine* of `self` and directly returns the computed value. In Debug mode this method may panic if the computation fails."]     ["Computes the *inverse hyperbolic cosine* 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 [`Error`] type."]  ["Computes and returns the *inverse hyperbolic cosine* of `self`.\n\nIn Debug mode this method may panic if the computation fails.\n\n# Panics\n\nIn Debug mode this method may panic if the computation fails. It is recommended to use the `try_acosh` method for safe computations."]  ["The error type that is returned by the `try_acosh` method."];
    [ASinH] [try_asinh] [asinh] ["Trait for computing the *inverse hyperbolic sine* of a number.\n\nThis trait defines methods for computing the *inverse hyperbolic sine* of a number. Provides 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_asinh` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n - `try_asinh`: Computes the *inverse hyperbolic sine* 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\n - `asinh`: Computes the *inverse hyperbolic sine* of `self` and directly returns the computed value. In Debug mode this method may panic if the computation fails."]             ["Computes the *inverse hyperbolic sine* 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 [`Error`] type."]    ["Computes and returns the *inverse hyperbolic sine* of `self`.\n\nIn Debug mode this method may panic if the computation fails.\n\n# Panics\n\nIn Debug mode this method may panic if the computation fails. It is recommended to use the `try_asinh` method for safe computations."]    ["The error type that is returned by the `try_asinh` method."];
    [ATanH] [try_atanh] [atanh] ["Trait for computing the *inverse hyperbolic tangent* of a number.\n\nThis trait defines methods for computing the *inverse hyperbolic tangent* of a number. Provides 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_atanh` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n - `try_atanh`: Computes the *inverse hyperbolic tangent* 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\n - `atanh`: Computes the *inverse hyperbolic tangent* of `self` and directly returns the computed value. In Debug mode this method may panic if the computation fails."] ["Computes the *inverse hyperbolic tangent* 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 [`Error`] type."] ["Computes and returns the *inverse hyperbolic tangent* of `self`.\n\nIn Debug mode this method may panic if the computation fails.\n\n# Panics\n\nIn Debug mode this method may panic if the computation fails. It is recommended to use the `try_atanh` method for safe computations."] ["The error type that is returned by the `try_atanh` method."];
    [CosH]  [try_cosh]  [cosh]  ["Trait for computing the *hyperbolic cosine* of a number.\n\nThis trait defines methods for computing the *hyperbolic cosine* of a number. Provides 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_cosh` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n - `try_cosh`: Computes the *hyperbolic cosine* 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\n - `cosh`: Computes the *hyperbolic cosine* of `self` and directly returns the computed value. In Debug mode this method may panic if the computation fails."]                                        ["Computes the *hyperbolic cosine* 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 [`Error`] type."]          ["Computes and returns the *hyperbolic cosine* of `self`.\n\nIn Debug mode this method may panic if the computation fails.\n\n# Panics\n\nIn Debug mode this method may panic if the computation fails. It is recommended to use the `try_cosh` method for safe computations."]           ["The error type that is returned by the `try_cosh` method."];
    [SinH]  [try_sinh]  [sinh]  ["Trait for computing the *hyperbolic sine* of a number.\n\nThis trait defines methods for computing the *hyperbolic sine* of a number. Provides 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_sinh` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n - `try_sinh`: Computes the *hyperbolic sine* 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\n - `sinh`: Computes the *hyperbolic sine* of `self` and directly returns the computed value. In Debug mode this method may panic if the computation fails."]                                                ["Computes the *hyperbolic sine* 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 [`Error`] type."]            ["Computes and returns the *hyperbolic sine* of `self`.\n\nIn Debug mode this method may panic if the computation fails.\n\n# Panics\n\nIn Debug mode this method may panic if the computation fails. It is recommended to use the `try_sinh` method for safe computations."]             ["The error type that is returned by the `try_sinh` method."]; 
    [TanH]  [try_tanh]  [tanh]  ["Trait for computing the *hyperbolic tangent* of a number.\n\nThis trait defines methods for computing the *hyperbolic tangent* of a number. Provides 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_tanh` method. This type must implement the [`std::error::Error`] trait.\n\n# Required Methods\n\n - `try_tanh`: Computes the *hyperbolic tangent* 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\n - `tanh`: Computes the *hyperbolic tangent* of the number and directly returns the computed value. In Debug mode this method may panic if the computation fails."]                                ["Computes the *hyperbolic tangent* 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 [`Error`] type."]         ["Computes and returns the *hyperbolic tangent* of `self`.\n\nIn Debug mode this method may panic if the computation fails.\n\n# Panics\n\nIn Debug mode this method may panic if the computation fails. It is recommended to use the `try_tanh` method for safe computations."]          ["The error type that is returned by the `try_tanh` method."];
)]
#[doc = trait_doc]
pub trait T: Sized {
    #[doc = err_doc]
    type Error: std::error::Error;

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

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

#[duplicate_item(
    T       E                 ErrIn                  try_func    func    scalar_type     ;
    [CosH]  [CosHErrors]      [CosHInputErrors]      [try_cosh]  [cosh]  [f64]           ;
    [CosH]  [CosHErrors]      [CosHInputErrors]      [try_cosh]  [cosh]  [Complex::<f64>];
    [SinH]  [SinHErrors]      [SinHInputErrors]      [try_sinh]  [sinh]  [f64]           ;
    [SinH]  [SinHErrors]      [SinHInputErrors]      [try_sinh]  [sinh]  [Complex::<f64>];
    [TanH]  [TanHRealErrors]  [TanHRealInputErrors]  [try_tanh]  [tanh]  [f64]           ;
    [ASinH] [ASinHErrors]     [ASinHInputErrors]     [try_asinh] [asinh] [f64]           ;
    [ASinH] [ASinHErrors]     [ASinHInputErrors]     [try_asinh] [asinh] [Complex::<f64>];
)]
impl T for scalar_type {
    type Error = E<scalar_type>;

    #[inline(always)]
    fn try_func(self) -> Result<Self, <Self as T>::Error> {
        StrictFinitePolicy::<scalar_type, 53>::validate(self)
            .map_err(|e| ErrIn::InvalidArgument { source: e }.into())
            .and_then(|v| {
                StrictFinitePolicy::<scalar_type, 53>::validate(scalar_type::func(v))
                    .map_err(|e| Self::Error::Output { source: e })
            })
    }

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

impl ATanH for f64 {
    type Error = ATanHErrors<f64>;

    #[inline(always)]
    fn try_atanh(self) -> Result<Self, Self::Error> {
        StrictFinitePolicy::<f64, 53>::validate(self)
            .map_err(|e| ATanHInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                // The domain of atanh is (-1, 1), so we check if the value is within this range.
                // If it is outside this range, we return an error.
                if v <= -1. || v >= 1. {
                    Err(ATanHInputErrors::OutOfDomain {
                        value: v,
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<f64, 53>::validate(f64::atanh(v))
                        .map_err(|e| ATanHErrors::Output { source: e })
                }
            })
    }

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

impl ACosH for f64 {
    type Error = ACosHErrors<f64>;

    #[inline(always)]
    fn try_acosh(self) -> Result<Self, <Self as ACosH>::Error> {
        StrictFinitePolicy::<f64, 53>::validate(self)
            .map_err(|e| ACosHInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                if v < 1.0 {
                    Err(ACosHInputErrors::OutOfDomain {
                        value: v,
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<f64, 53>::validate(f64::acosh(v))
                        .map_err(|e| ACosHErrors::Output { source: e })
                }
            })
    }

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

impl TanH for Complex<f64> {
    type Error = TanHComplexErrors<Complex<f64>>;

    #[inline(always)]
    fn try_tanh(self) -> Result<Self, Self::Error> {
        StrictFinitePolicy::<Self, 53>::validate(self)
            .map_err(|e| TanHComplexInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                // The domain of tanh is C minus the set of points where cosh is zero.
                if RawScalarTrait::is_zero(&v.cosh()) {
                    Err(TanHComplexInputErrors::OutOfDomain {
                        value: v,
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<Self, 53>::validate(Complex::<f64>::tanh(v))
                        .map_err(|e| TanHComplexErrors::Output { source: e })
                }
            })
    }

    #[inline(always)]
    fn tanh(self) -> Self {
        #[cfg(debug_assertions)]
        {
            self.try_tanh().unwrap()
        }
        #[cfg(not(debug_assertions))]
        {
            Complex::<f64>::tanh(self)
        }
    }
}

impl ATanH for Complex<f64> {
    type Error = ATanHErrors<Complex<f64>>;

    #[inline(always)]
    fn try_atanh(self) -> Result<Self, Self::Error> {
        StrictFinitePolicy::<Self, 53>::validate(self)
            .map_err(|e| ATanHInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                // The principal value of atanh(z) has branch cuts on the real axis for x <= -1 and x >= 1.
                if v.im == 0. && (v.re <= -1. || v.re >= 1.) {
                    Err(ATanHInputErrors::OutOfDomain {
                        value: v,
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<Self, 53>::validate(Complex::<f64>::atanh(v))
                        .map_err(|e| ATanHErrors::Output { source: e })
                }
            })
    }

    #[inline(always)]
    fn atanh(self) -> Self {
        #[cfg(debug_assertions)]
        {
            self.try_atanh().unwrap()
        }
        #[cfg(not(debug_assertions))]
        {
            Complex::<f64>::atanh(self)
        }
    }
}

impl ACosH for Complex<f64> {
    type Error = ACosHErrors<Complex<f64>>;

    #[inline(always)]
    fn try_acosh(self) -> Result<Self, <Self as ACosH>::Error> {
        StrictFinitePolicy::<Complex<f64>, 53>::validate(self)
            .map_err(|e| ACosHInputErrors::InvalidArgument { source: e }.into())
            .and_then(|v| {
                if v.im == 0.0 && v.re < 1.0 {
                    Err(ACosHInputErrors::OutOfDomain {
                        value: v,
                        backtrace: capture_backtrace(),
                    }
                    .into())
                } else {
                    StrictFinitePolicy::<Complex<f64>, 53>::validate(Complex::<f64>::acosh(v))
                        .map_err(|e| ACosHErrors::Output { source: e })
                }
            })
    }

    #[inline(always)]
    fn acosh(self) -> Self {
        #[cfg(debug_assertions)]
        {
            self.try_acosh().unwrap()
        }
        #[cfg(not(debug_assertions))]
        {
            Complex::<f64>::acosh(self)
        }
    }
}

//------------------------------------------------------------------------------------------------

//------------------------------------------------------------------------------------------------
/// A convenience trait that aggregates the standard hyperbolic functions and their inverses.
///
/// This trait serves as a shorthand for requiring a type to implement all the fundamental
/// hyperbolic operations:
/// - [`SinH`] and [`ASinH`]
/// - [`CosH`] and [`ACosH`]
/// - [`TanH`] and [`ATanH`]
///
/// 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 hyperbolic
/// capabilities. By using `HyperbolicFunctions` as a bound, you can write generic functions that
/// utilize any of its constituent trait methods.
///
/// # Examples
///
/// ```
/// use num_valid::functions::{HyperbolicFunctions, Abs};
/// use std::ops::{Mul, Sub};
///
/// // A generic function that verifies the identity cosh(x)^2 - sinh(x)^2 = 1.
/// // We bound T by HyperbolicFunctions, Clone, and arithmetic ops.
/// fn verify_hyperbolic_identity<T>(x: T) -> T
/// where
///     T: HyperbolicFunctions + Clone + Sub<Output = T> + Mul<Output = T>,
/// {
///     let cosh_x = x.clone().cosh();
///     let sinh_x = x.sinh();
///     // This works because FpScalar requires the necessary arithmetic traits.
///     cosh_x.clone() * cosh_x - sinh_x.clone() * sinh_x
/// }
///
/// let value = 0.5f64;
/// let identity = verify_hyperbolic_identity(value);
///
/// // The result should be very close to 1.0.
/// // We use the Abs trait for comparison.
/// assert!((identity - 1.0).abs() < 1e-12);
/// ```
pub trait HyperbolicFunctions: SinH + ASinH + CosH + ACosH + TanH + ATanH {}

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

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

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

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

    mod sinh {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_sinh_valid() {
                    let value = 1.0;
                    let expected_result = 1.1752011936438014;
                    assert_eq!(value.try_sinh().unwrap(), expected_result);
                    assert_eq!(<f64 as SinH>::sinh(value), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }

                #[test]
                fn test_f64_sinh_negative() {
                    let value = -1.0;
                    let expected_result = -1.1752011936438014;
                    assert_eq!(value.try_sinh().unwrap(), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }

                #[test]
                fn test_f64_sinh_zero() {
                    let value = 0.0;
                    assert_eq!(value.try_sinh().unwrap(), 0.0);
                    assert_eq!(value.sinh(), 0.0);
                }

                #[test]
                fn test_f64_sinh_nan() {
                    let value = f64::NAN;
                    let result = value.try_sinh();
                    assert!(matches!(result, Err(SinHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_sinh_infinity() {
                    let value = f64::INFINITY;
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_sinh_subnormal() {
                    let value = f64::MIN_POSITIVE / 2.0;
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn sinh_valid() {
                    let value = Complex::new(1.0, 1.0);
                    let expected_result = Complex::new(0.6349639147847361, 1.2984575814159773);
                    assert_eq!(value.try_sinh().unwrap(), expected_result);
                    assert_eq!(<Complex<f64> as SinH>::sinh(value), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }

                #[test]
                fn sinh_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.try_sinh().unwrap(), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }

                #[test]
                fn sinh_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::NAN);
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));
                }

                #[test]
                fn sinh_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::INFINITY);
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));
                }

                #[test]
                fn sinh_subnormal() {
                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                    assert!(matches!(value.try_sinh(), Err(SinHErrors::Input { .. })));
                }
            }
        }

        #[cfg(feature = "rug")]
        mod rug53 {
            use super::*;
            use crate::kernels::rug::{ComplexRugStrictFinite, RealRugStrictFinite};
            use try_create::TryNew;

            mod real {
                use super::*;

                #[test]
                fn test_rug_float_sinh_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();

                    let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                        53,
                        1.1752011936438014,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().try_sinh().unwrap(), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }

                #[test]
                fn test_rug_float_sinh_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();

                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().try_sinh().unwrap(), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_rug_float_sinh_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 1.0), rug::Float::with_val(53, 1.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (
                                rug::Float::with_val(53, 0.6349639147847361),
                                rug::Float::with_val(53, 1.2984575814159773),
                            ),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_sinh().unwrap(), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }

                #[test]
                fn test_complex_rug_float_sinh_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (rug::Float::with_val(53, 0.), rug::Float::with_val(53, 0.)),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_sinh().unwrap(), expected_result);
                    assert_eq!(value.sinh(), expected_result);
                }
            }
        }
    }

    mod cosh {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_cosh_valid() {
                    let value = 1.0;
                    let expected_result = 1.5430806348152437;
                    assert_eq!(value.try_cosh().unwrap(), expected_result);
                    assert_eq!(<f64 as CosH>::cosh(value), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }

                #[test]
                fn test_f64_cosh_negative() {
                    let value = -1.0;
                    let expected_result = 1.5430806348152437;
                    assert_eq!(value.try_cosh().unwrap(), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }

                #[test]
                fn test_f64_cosh_zero() {
                    let value = 0.0;
                    assert_eq!(value.try_cosh().unwrap(), 1.0);
                    assert_eq!(value.cosh(), 1.0);
                }

                #[test]
                fn test_f64_cosh_nan() {
                    let value = f64::NAN;
                    let result = value.try_cosh();
                    assert!(matches!(result, Err(CosHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_cosh_infinity() {
                    let value = f64::INFINITY;
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_cosh_subnormal() {
                    let value = f64::MIN_POSITIVE / 2.0;
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn cosh_valid() {
                    let value = Complex::new(1.0, 1.0);
                    let expected_result = Complex::new(0.8337300251311491, 0.9888977057628651);
                    assert_eq!(value.try_cosh().unwrap(), expected_result);
                    assert_eq!(<Complex<f64> as CosH>::cosh(value), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }

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

                #[test]
                fn cosh_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::NAN);
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));
                }

                #[test]
                fn cosh_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::INFINITY);
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));
                }

                #[test]
                fn cosh_subnormal() {
                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                    assert!(matches!(value.try_cosh(), Err(CosHErrors::Input { .. })));
                }
            }
        }

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

            mod real {
                use super::*;

                #[test]
                fn test_rug_float_cosh_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();

                    let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                        53,
                        1.5430806348152437,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().try_cosh().unwrap(), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }

                #[test]
                fn test_rug_float_cosh_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();

                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();
                    assert_eq!(value.clone().try_cosh().unwrap(), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_rug_float_cosh_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 1.0), rug::Float::with_val(53, 1.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (
                                rug::Float::with_val(53, 0.833730025131149),
                                rug::Float::with_val(53, 0.9888977057628651),
                            ),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_cosh().unwrap(), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }

                #[test]
                fn test_complex_rug_float_cosh_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (rug::Float::with_val(53, 1.0), rug::Float::with_val(53, 0.0)),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_cosh().unwrap(), expected_result);
                    assert_eq!(value.cosh(), expected_result);
                }
            }
        }
    }

    mod tanh {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_tanh_valid() {
                    let value = 1.0;
                    let expected_result = 0.7615941559557649;
                    assert_eq!(value.try_tanh().unwrap(), expected_result);
                    assert_eq!(<f64 as TanH>::tanh(value), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }

                #[test]
                fn test_f64_tanh_negative() {
                    let value = -1.0;
                    let expected_result = -0.7615941559557649;
                    assert_eq!(value.try_tanh().unwrap(), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }

                #[test]
                fn test_f64_tanh_zero() {
                    let value = 0.0;
                    assert_eq!(value.try_tanh().unwrap(), 0.0);
                    assert_eq!(value.tanh(), 0.0);
                }

                #[test]
                fn test_f64_tanh_nan() {
                    let value = f64::NAN;
                    let result = value.try_tanh();
                    assert!(matches!(result, Err(TanHRealErrors::Input { .. })));
                }

                #[test]
                fn test_f64_tanh_infinity() {
                    let value = f64::INFINITY;
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHRealErrors::Input { .. })
                    ));
                }

                #[test]
                fn test_f64_tanh_subnormal() {
                    let value = f64::MIN_POSITIVE / 2.0;
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHRealErrors::Input { .. })
                    ));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn tanh_valid() {
                    let value = Complex::new(1.0, 1.0);
                    let expected_result = if cfg!(target_arch = "x86_64") {
                        Complex::new(1.0839233273386948, 0.27175258531951174)
                    } else if cfg!(target_arch = "aarch64") {
                        Complex::new(1.0839233273386946, 0.27175258531951174)
                    } else {
                        todo!("Unknown target architecture");
                    };

                    assert_eq!(value.try_tanh().unwrap(), expected_result);
                    assert_eq!(<Complex<f64> as TanH>::tanh(value), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }

                #[test]
                fn tanh_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.try_tanh().unwrap(), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }

                #[test]
                fn tanh_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHComplexErrors::Input { .. })
                    ));

                    let value = Complex::new(0.0, f64::NAN);
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHComplexErrors::Input { .. })
                    ));
                }

                #[test]
                fn tanh_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHComplexErrors::Input { .. })
                    ));

                    let value = Complex::new(0.0, f64::INFINITY);
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHComplexErrors::Input { .. })
                    ));
                }

                #[test]
                fn tanh_subnormal() {
                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHComplexErrors::Input { .. })
                    ));

                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                    assert!(matches!(
                        value.try_tanh(),
                        Err(TanHComplexErrors::Input { .. })
                    ));
                }
            }
        }

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

            mod real {
                use super::*;

                #[test]
                fn test_rug_float_tanh_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();

                    let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                        53,
                        0.7615941559557649,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().try_tanh().unwrap(), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }

                #[test]
                fn test_rug_float_tanh_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();

                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().try_tanh().unwrap(), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_rug_float_tanh_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.5), rug::Float::with_val(53, 0.5)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (
                                rug::Float::with_val(53, 5.640831412674985e-1),
                                rug::Float::with_val(53, 4.0389645531602575e-1),
                            ),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_tanh().unwrap(), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }

                #[test]
                fn test_complex_rug_float_tanh_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_tanh().unwrap(), expected_result);
                    assert_eq!(value.tanh(), expected_result);
                }
            }
        }
    }

    mod asinh {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_asinh_valid() {
                    let value = 1.0;
                    let expected_result = 0.881373587019543;
                    assert_eq!(value.try_asinh().unwrap(), expected_result);
                    assert_eq!(<f64 as ASinH>::asinh(value), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }

                #[test]
                fn test_f64_asinh_negative() {
                    let value = -1.0;
                    let expected_result = -0.881373587019543;
                    assert_eq!(value.try_asinh().unwrap(), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }

                #[test]
                fn test_f64_asinh_zero() {
                    let value = 0.0;
                    assert_eq!(value.try_asinh().unwrap(), 0.0);
                    assert_eq!(value.asinh(), 0.0);
                }

                #[test]
                fn test_f64_asinh_nan() {
                    let value = f64::NAN;
                    let result = value.try_asinh();
                    assert!(matches!(result, Err(ASinHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_asinh_infinity() {
                    let value = f64::INFINITY;
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_asinh_subnormal() {
                    let value = f64::MIN_POSITIVE / 2.0;
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn asinh_valid() {
                    let value = Complex::new(1.0, 1.0);
                    let expected_result = Complex::new(1.0612750619050357, 0.6662394324925153);
                    assert_eq!(value.try_asinh().unwrap(), expected_result);
                    assert_eq!(<Complex<f64> as ASinH>::asinh(value), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }

                #[test]
                fn asinh_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.try_asinh().unwrap(), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }

                #[test]
                fn asinh_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::NAN);
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));
                }

                #[test]
                fn asinh_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::INFINITY);
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));
                }

                #[test]
                fn asinh_subnormal() {
                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                    assert!(matches!(value.try_asinh(), Err(ASinHErrors::Input { .. })));
                }
            }
        }

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

            mod real {
                use super::*;

                #[test]
                fn test_rug_float_asinh_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();

                    let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                        53,
                        0.881373587019543,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().try_asinh().unwrap(), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }

                #[test]
                fn test_rug_float_asinh_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();

                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().try_asinh().unwrap(), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_rug_float_asinh_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 1.0), rug::Float::with_val(53, 1.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (
                                rug::Float::with_val(53, 1.0612750619050357),
                                rug::Float::with_val(53, 0.6662394324925153),
                            ),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_asinh().unwrap(), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }

                #[test]
                fn test_complex_rug_float_asinh_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (rug::Float::with_val(53, 0.), rug::Float::with_val(53, 0.)),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_asinh().unwrap(), expected_result);
                    assert_eq!(value.asinh(), expected_result);
                }
            }
        }
    }

    mod acosh {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_acosh_valid() {
                    let value = 1.5;
                    let expected_result = 0.9624236501192069;
                    assert_eq!(value.try_acosh().unwrap(), expected_result);
                    assert_eq!(<f64 as ACosH>::acosh(value), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }

                #[test]
                fn test_f64_acosh_out_of_domain() {
                    let value = 0.9;
                    let err = value.try_acosh().unwrap_err();
                    assert!(matches!(
                        err,
                        ACosHErrors::Input {
                            source: ACosHInputErrors::OutOfDomain { value: 0.9, .. }
                        }
                    ));
                }

                #[test]
                fn test_f64_acosh_one() {
                    let value = 1.0;
                    let expected_result = 0.0;
                    assert_eq!(value.try_acosh().unwrap(), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }

                #[test]
                fn test_f64_acosh_nan() {
                    let value = f64::NAN;
                    let result = value.try_acosh();
                    assert!(matches!(result, Err(ACosHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_acosh_infinity() {
                    let value = f64::INFINITY;
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_acosh_subnormal() {
                    let value = f64::MIN_POSITIVE / 2.0;
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn acosh_valid() {
                    let value = Complex::new(1.5, 1.5);
                    let expected_result = if cfg!(target_arch = "x86_64") {
                        Complex::new(1.44973434958536, 0.840395108841671)
                    } else if cfg!(target_arch = "aarch64") {
                        Complex::new(1.4497343495853596, 0.8403951088416711)
                    } else {
                        todo!("Unknown target architecture");
                    };

                    assert_eq!(value.try_acosh().unwrap(), expected_result);
                    assert_eq!(<Complex<f64> as ACosH>::acosh(value), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }

                #[test]
                fn acosh_out_of_domain() {
                    let v = Complex::new(-1.1, 0.);
                    let err = v.try_acosh().unwrap_err();
                    assert!(matches!(
                        err,
                        ACosHErrors::Input {
                            source: ACosHInputErrors::OutOfDomain { .. }
                        }
                    ));
                }

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

                #[test]
                fn acosh_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::NAN);
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));
                }

                #[test]
                fn acosh_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::INFINITY);
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));
                }

                #[test]
                fn acosh_subnormal() {
                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                    assert!(matches!(value.try_acosh(), Err(ACosHErrors::Input { .. })));
                }
            }
        }

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

            mod real {
                use super::*;

                #[test]
                fn test_rug_float_acosh_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.5)).unwrap();

                    let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                        53,
                        0.9624236501192069,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().try_acosh().unwrap(), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }

                #[test]
                fn test_rug_float_acosh_one() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();

                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().try_acosh().unwrap(), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_rug_float_acosh_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 1.5), rug::Float::with_val(53, 1.5)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (
                                rug::Float::with_val(53, 1.4497343495853598),
                                rug::Float::with_val(53, 8.403951088416711e-1),
                            ),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_acosh().unwrap(), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }

                #[test]
                fn test_complex_rug_float_acosh_one() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 1.0), rug::Float::with_val(53, 0.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_acosh().unwrap(), expected_result);
                    assert_eq!(value.acosh(), expected_result);
                }
            }
        }
    }

    mod atanh {
        use super::*;

        mod native64 {
            use super::*;

            mod real {
                use super::*;

                #[test]
                fn test_f64_atanh_valid() {
                    let value = 0.5;
                    let expected_result = if cfg!(target_arch = "x86_64") {
                        0.5493061443340548
                    } else if cfg!(target_arch = "aarch64") {
                        0.5493061443340549
                    } else {
                        todo!("Unknown target architecture");
                    };

                    assert_eq!(value.try_atanh().unwrap(), expected_result);
                    assert_eq!(<f64 as ATanH>::atanh(value), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }

                #[test]
                fn test_f64_atanh_negative() {
                    let value = -0.5;
                    let expected_result = -0.5493061443340548;
                    assert_eq!(value.try_atanh().unwrap(), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }

                #[test]
                fn test_f64_atanh_out_of_domain() {
                    let value = 1.0;
                    let err = value.try_atanh().unwrap_err();
                    assert!(matches!(
                        err,
                        ATanHErrors::Input {
                            source: ATanHInputErrors::OutOfDomain { value: 1., .. }
                        }
                    ));
                }

                #[test]
                fn test_f64_atanh_zero() {
                    let value = 0.0;
                    assert_eq!(value.try_atanh().unwrap(), 0.0);
                    assert_eq!(value.atanh(), 0.0);
                }

                #[test]
                fn test_f64_atanh_nan() {
                    let value = f64::NAN;
                    let result = value.try_atanh();
                    assert!(matches!(result, Err(ATanHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_atanh_infinity() {
                    let value = f64::INFINITY;
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));
                }

                #[test]
                fn test_f64_atanh_subnormal() {
                    let value = f64::MIN_POSITIVE / 2.0;
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn atanh_valid() {
                    let value = Complex::new(0.5, 0.5);
                    let expected_result = Complex::new(0.4023594781085251, 0.5535743588970452);
                    assert_eq!(value.try_atanh().unwrap(), expected_result);
                    assert_eq!(<Complex<f64> as ATanH>::atanh(value), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }

                #[test]
                fn atanh_out_of_domain() {
                    let v = Complex::new(-1., 0.);
                    let err = v.try_atanh().unwrap_err();
                    assert!(matches!(
                        err,
                        ATanHErrors::Input {
                            source: ATanHInputErrors::OutOfDomain { .. }
                        }
                    ));
                }

                #[test]
                fn atanh_zero() {
                    let value = Complex::new(0.0, 0.0);
                    let expected_result = Complex::new(0.0, 0.0);
                    assert_eq!(value.try_atanh().unwrap(), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }

                #[test]
                fn atanh_nan() {
                    let value = Complex::new(f64::NAN, 0.0);
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::NAN);
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));
                }

                #[test]
                fn atanh_infinity() {
                    let value = Complex::new(f64::INFINITY, 0.0);
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::INFINITY);
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));
                }

                #[test]
                fn atanh_subnormal() {
                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));

                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                    assert!(matches!(value.try_atanh(), Err(ATanHErrors::Input { .. })));
                }
            }
        }

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

            mod real {
                use super::*;

                #[test]
                fn test_rug_float_atanh_valid() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.5)).unwrap();

                    let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                        53,
                        0.5493061443340549,
                    ))
                    .unwrap();
                    assert_eq!(value.clone().try_atanh().unwrap(), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }

                #[test]
                fn test_rug_float_atanh_zero() {
                    let value =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();

                    let expected_result =
                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
                    assert_eq!(value.clone().try_atanh().unwrap(), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }
            }

            mod complex {
                use super::*;

                #[test]
                fn test_complex_rug_float_atanh_valid() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.5), rug::Float::with_val(53, 0.5)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (
                                rug::Float::with_val(53, 0.40235947810852507),
                                rug::Float::with_val(53, 0.5535743588970452),
                            ),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_atanh().unwrap(), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }

                #[test]
                fn test_complex_rug_float_atanh_zero() {
                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                    ))
                    .unwrap();

                    let expected_result =
                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                            53,
                            (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                        ))
                        .unwrap();
                    assert_eq!(value.clone().try_atanh().unwrap(), expected_result);
                    assert_eq!(value.atanh(), expected_result);
                }
            }
        }
    }
}
//------------------------------------------------------------------------------------------------
