decimal-scaled 0.4.1

Const-generic base-10 fixed-point decimals (D9/D18/D38/D76/D153/D307) with integer-only transcendentals correctly rounded to within 0.5 ULP — exact at the type's last representable place. Deterministic across every platform; no_std-friendly.
Documentation
//! D38 trig (forward + inverse + atan2) via widen → D57 → narrow back.
//!
//! See [`crate::algos::ln::borrow_d57`] for the broader rationale.
//!
//! - `sin` / `cos` / `tan` / `atan` route through
//!   [`crate::algos::trig::wide_kernel`]'s D57 free functions.
//! - `asin` / `acos` / `atan2` route through D57's inherent
//!   `*_strict_with` methods (no separate algos kernel today; this
//!   mirrors how the wide-tier `TrigPolicy` impls invoke them).
//!
//! Correctness: all of these have outputs bounded within
//! `[-π, π]` (atan2) or smaller, so the narrowing `TryFrom` cannot fail
//! on a representable input. `tan(±π/2)` panics on the D57 side via
//! `wide_kernel::tan_strict_d57` (the same logical panic D38's
//! `fixed_d38::tan_strict` produces).

use crate::types::widths::{D38, D57};
use crate::support::rounding::RoundingMode;
use crate::wide_int::I192;

#[inline]
fn narrow<const SCALE: u32>(raw_wide: I192, op: &'static str) -> i128 {
    let wide = D57::<SCALE>::from_bits(raw_wide);
    let r: D38<SCALE> = wide.try_into().unwrap_or_else(|_| panic!(
        "{op}: result out of range — produced {wide}, D38<{SCALE}> represents only |x| < 1.7e{}",
        38_i32 - SCALE as i32,
    ));
    r.0
}

// ── forward (sin / cos / tan / atan) — via wide_kernel free fns ─────

#[inline]
#[must_use]
pub(crate) fn sin_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
    let widened: D57<SCALE> = D38::<SCALE>::from_bits(raw).into();
    let raw_wide = super::wide_kernel::sin_strict_d57(widened.0, mode, SCALE);
    narrow::<SCALE>(raw_wide, "sin_strict")
}

#[inline]
#[must_use]
pub(crate) fn cos_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
    let widened: D57<SCALE> = D38::<SCALE>::from_bits(raw).into();
    let raw_wide = super::wide_kernel::cos_strict_d57(widened.0, mode, SCALE);
    narrow::<SCALE>(raw_wide, "cos_strict")
}

#[inline]
#[must_use]
pub(crate) fn tan_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
    let widened: D57<SCALE> = D38::<SCALE>::from_bits(raw).into();
    let raw_wide = super::wide_kernel::tan_strict_d57(widened.0, mode, SCALE);
    narrow::<SCALE>(raw_wide, "tan_strict")
}

#[inline]
#[must_use]
pub(crate) fn atan_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
    let widened: D57<SCALE> = D38::<SCALE>::from_bits(raw).into();
    let raw_wide = super::wide_kernel::atan_strict_d57(widened.0, mode, SCALE);
    narrow::<SCALE>(raw_wide, "atan_strict")
}

// ── inverse (asin / acos / atan2) — via D57 inherent methods ────────

#[inline]
#[must_use]
pub(crate) fn asin_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
    let widened: D57<SCALE> = D38::<SCALE>::from_bits(raw).into();
    let result = widened.asin_strict_with(mode);
    narrow::<SCALE>(result.0, "asin_strict")
}

#[inline]
#[must_use]
pub(crate) fn acos_strict<const SCALE: u32>(raw: i128, mode: RoundingMode) -> i128 {
    let widened: D57<SCALE> = D38::<SCALE>::from_bits(raw).into();
    let result = widened.acos_strict_with(mode);
    narrow::<SCALE>(result.0, "acos_strict")
}

#[inline]
#[must_use]
pub(crate) fn atan2_strict<const SCALE: u32>(y_raw: i128, x_raw: i128, mode: RoundingMode) -> i128 {
    let y_wide: D57<SCALE> = D38::<SCALE>::from_bits(y_raw).into();
    let x_wide: D57<SCALE> = D38::<SCALE>::from_bits(x_raw).into();
    let result = y_wide.atan2_strict_with(x_wide, mode);
    narrow::<SCALE>(result.0, "atan2_strict")
}