// SPDX-FileCopyrightText: 2026 John Moxley
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Mathematical constants and float-compatibility constants for every
//! decimal width.
//!
//! # Constants provided
//!
//! The [`DecimalConstants`] trait exposes `pi`, `tau`, `half_pi`,
//! `quarter_pi`, `golden`, and `e` as methods on every width. A single
//! generic impl over `D<Int<N>, SCALE>` serves every width, sourced from
//! the unified per-scale table in [`crate::consts`].
//!
//! Two inherent associated constants, `EPSILON` and `MIN_POSITIVE`, are
//! provided as analogues to `f64::EPSILON` and `f64::MIN_POSITIVE` so
//! that generic code parameterised over numeric types continues to
//! compile when `T` is any of the decimal widths.
//!
//! # Precision strategy
//!
//! Constants are sourced from an oracle-computed per-scale table — no
//! `f64` anywhere. Each entry is
//! `floor(const × 10^SCALE)` (narrowest-fit limbs) plus a `round_up`
//! bit, from which the correct rounding under any [`RoundingMode`] is
//! derived directly — `Trunc`/`Floor` = floor, `Ceiling` = floor+1, and
//! the three half-modes = floor + round_up (the constants are
//! irrational, so the dropped tail is never an exact tie). The selected
//! work-integer value is then checked against the type's storage range.
//! Because the entry is already keyed on the
//! caller's `SCALE` there is no rescale step and no precision loss:
//!
//! **0.5 ULP at every supported scale**, with no exceptions in the
//! precision contract. The only constraint is the width's *storage
//! range*: a value that mathematically exceeds the type's
//! `Storage::MAX / 10^SCALE` cannot be represented at all. At
//! `D38<38>` the storage range is approximately ±1.70141, so the three
//! larger-magnitude constants — `pi ≈ 3.14159`, `tau ≈ 6.28318`,
//! `e ≈ 2.71828` — overflow `i128` and the corresponding methods panic
//! with a clear "constant out of storage range" message;
//! `half_pi ≈ 1.57080`, `quarter_pi ≈ 0.78540`, and `golden ≈ 1.61803`
//! all fit inside ±1.70141 and remain correctly-rounded to 0.5 ULP.
//!
//! [`RoundingMode`]: crate::support::rounding::RoundingMode
//!
//! # Sources
//!
//! The constant table is generated by `scripts/gen_const_table.py`
//! (mpmath oracle): pi/tau/pi-2/pi-4 (ISO 80000-2), e (OEIS A001113),
//! golden ratio (OEIS A001622), plus deg_per_rad (180/pi) and
//! rad_per_deg (pi/180).



pub use crate::types::traits::consts::DecimalConstants;


/// Unwraps a storage-range-checked constant, panicking with the canonical
/// "out of storage range" message when the value does not fit `Int<N>` at the
/// requested `SCALE`. Generic over the storage width — the single
/// [`DecimalConstants`] impl below uses it for every tier.
#[inline]
pub(crate) fn checked_storage<const N: usize>(
    value: Option<crate::int::types::Int<N>>,
    name: &'static str,
    scale: u32,
) -> crate::int::types::Int<N> {
    match value {
        Some(v) => v,
        None => panic!(
            "constant out of storage range: {name} cannot fit storage at SCALE = {scale} \
             (storage range is +/- Int::<N>::MAX / 10^SCALE)"
        ),
    }
}

// ─── The SINGLE DecimalConstants impl for EVERY decimal width ──────────
//
// One generic impl over `D<Int<N>, SCALE>` replaces all the former per-tier
// impls — the narrow `decl_decimal_consts!` macro (D18 = Int<1>, D38 = Int<2>),
// the wide hand impls (D76/D153/D307 = Int<4>/<8>/<16>), and the
// `decl_wide_consts_tier!` macro (D57/D115/D230/D462/D616/D924/D1232) — and the
// System-B `*_RAW_*` / build-time `wide_consts.rs` path they were sourced from.
//
// Every constant is sourced from the unified per-scale table (`crate::consts`)
// at the caller's const `SCALE`, range-CHECKED to the storage `Int<N>`: a value
// past the tier's representable scale (pi/tau/e at a scale whose 10^scale lift
// exceeds `Int<N>`, or deg_per_rad ~57.3 near a tier's top) panics with the
// canonical "out of storage range" message, matching the prior per-tier impls.
// The lookup is keyed on the const `SCALE`, so it folds to the one entry.
//
// We deliberately do NOT use the strong-fold `*_const_n` `const {}` bake here:
// a `const {}` that overflows would be a COMPILE error, whereas an over-scale
// request must be a RUNTIME panic — so the checked runtime accessor is the
// behaviour-preserving choice. The internal kernel path (ln/exp/trig) uses the
// unchecked `*_by_scale` / `*_by_working_scale` into a wide work integer.
impl<const N: usize, const SCALE: u32> DecimalConstants
    for crate::D<crate::int::types::Int<N>, SCALE>
{
    #[inline]
    fn pi() -> Self {
        Self::pi_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn tau() -> Self {
        Self::tau_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn half_pi() -> Self {
        Self::half_pi_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn quarter_pi() -> Self {
        Self::quarter_pi_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn golden() -> Self {
        Self::golden_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn e() -> Self {
        Self::e_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn deg_per_rad() -> Self {
        Self::deg_per_rad_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn rad_per_deg() -> Self {
        Self::rad_per_deg_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }
    #[inline]
    fn log10_2() -> Self {
        Self::log10_2_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
    }

    #[inline]
    fn pi_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::pi_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "pi",
            SCALE,
        ))
    }
    #[inline]
    fn tau_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::tau_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "tau",
            SCALE,
        ))
    }
    #[inline]
    fn half_pi_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::half_pi_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "half_pi",
            SCALE,
        ))
    }
    #[inline]
    fn quarter_pi_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::quarter_pi_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "quarter_pi",
            SCALE,
        ))
    }
    #[inline]
    fn golden_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::golden_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "golden",
            SCALE,
        ))
    }
    #[inline]
    fn e_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::e_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "e",
            SCALE,
        ))
    }
    #[inline]
    fn deg_per_rad_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::deg_per_rad_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "deg_per_rad",
            SCALE,
        ))
    }
    #[inline]
    fn rad_per_deg_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::rad_per_deg_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "rad_per_deg",
            SCALE,
        ))
    }
    #[inline]
    fn log10_2_with(mode: crate::support::rounding::RoundingMode) -> Self {
        Self(checked_storage::<N>(
            crate::consts::log10_2_by_scale_checked::<crate::int::types::Int<N>>(SCALE, mode),
            "log10_2",
            SCALE,
        ))
    }
}

// EPSILON / MIN_POSITIVE for every width are now emitted by
// `decl_decimal_basics!`. The D38-specific inherent impl that used
// to live here has been removed in favour of the macro-emitted ones
// so D18 / D38 / D57 / D76 / D115 / D153 / D230 / D307 / D462 /
// D616 / D924 / D1232 all share the same EPSILON / MIN_POSITIVE
// surface.
