decimal_scaled/types/checked_transcendentals.rs
1//! `checked_*` siblings of the strict transcendental family.
2//!
3//! One generic `impl` over `(N, SCALE)` — a single source serving every
4//! width tier (D18 .. D1232), per the overflow contract in
5//! `docs/ARCHITECTURE.md` ("Overflow & domain behaviour"): the default
6//! strict form panics on a domain error or an out-of-range result; the
7//! `checked_` form returns `None` instead. The two forms run the SAME
8//! policy-dispatched kernel, so an in-range `checked_*` result is
9//! bit-identical to the default form's result.
10//!
11//! # Shape
12//!
13//! Every strict transcendental gets the pair
14//!
15//! - `checked_<fn>_strict_with(self, .., mode) -> Option<Self>`
16//! - `checked_<fn>_strict(self, ..) -> Option<Self>` — the default-mode
17//! sibling, delegating with [`DEFAULT_ROUNDING_MODE`].
18//!
19//! Only the **strict** forms get checked siblings: the architecture's
20//! claim covers the strict transcendentals (the f64-bridge `*_fast`
21//! forms have no panic contract to opt out of — they saturate — and the
22//! `*_approx` forms trade away the strict guarantee by construction).
23//!
24//! # What `None` covers, per method class
25//!
26//! - **Total methods** (`sqrt`, `cbrt`, `sin`, `cos`, `atan`, `atan2`,
27//! `tanh`, `asinh`, `to_radians`): the default form cannot panic — the
28//! result is mathematically bounded well inside every tier's range at
29//! every valid scale (each method's doc carries the bound) — so the
30//! checked form always returns `Some`.
31//! - **Domain-checked methods** (`asin`, `acos`, `acosh`, `ln`, `log`,
32//! `log2`, `log10`, `atanh`): `None` exactly on the inputs the default
33//! form rejects with a domain panic.
34//! - **Range-checked methods** (`exp`, `ln`, `hypot`, …): `None` when
35//! the correctly-rounded result does not fit the storage range — the
36//! same single detection point whose `unwrap` is the default form's
37//! panic (see the per-policy `checked_dispatch` primitives).
38//!
39//! A method's doc states which of these apply. Where the out-of-range
40//! seam has not yet been threaded through a kernel family, the doc says
41//! so explicitly: those methods still panic on an out-of-range result
42//! (identically to the default form — never a silent wrong value).
43//!
44//! [`DEFAULT_ROUNDING_MODE`]: crate::support::rounding::DEFAULT_ROUNDING_MODE
45
46use crate::int::types::compute_limbs::{ComputeLimbs, Limbs};
47use crate::int::types::Int;
48use crate::support::rounding::{RoundingMode, DEFAULT_ROUNDING_MODE};
49
50// `private_bounds`: the sqrt / cbrt / hypot methods carry the same
51// `Limbs<N>: ComputeLimbs` scratch bound their policy dispatchers do.
52// The trait is crate-internal plumbing; at every concrete `Dxx<S>` the
53// bound is auto-satisfied and invisible to callers, so the unnameable
54// bound never surfaces in downstream code.
55#[allow(private_bounds)]
56impl<const N: usize, const SCALE: u32> crate::D<Int<N>, SCALE> {
57 /// Raw-storage value of `1` at this scale (`10^SCALE`), the unit the
58 /// domain walls compare against.
59 #[inline]
60 fn unit_bits() -> Int<N> {
61 const { Int::<N>::TEN.pow(SCALE) }
62 }
63
64 // ── Logarithms ────────────────────────────────────────────────
65
66 /// Checked [`ln_strict_with`](crate::types::widths::D38::ln_strict_with):
67 /// natural logarithm, `None` instead of a panic.
68 ///
69 /// Returns `None` when `self <= 0` (the domain wall) or when the
70 /// correctly-rounded result does not fit the storage range (possible
71 /// only near a tier's maximum scale). Otherwise
72 /// `Some(self.ln_strict_with(mode))`, bit-identical.
73 ///
74 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
75 /// out-of-range result still panics (kernel seam not yet reached).
76 /// Domain errors return `None` at every tier.
77 ///
78 /// ```
79 /// use decimal_scaled::{D38, RoundingMode};
80 /// let two = D38::<12>::try_from(2i64).unwrap();
81 /// assert_eq!(
82 /// two.checked_ln_strict_with(RoundingMode::HalfToEven),
83 /// Some(two.ln_strict_with(RoundingMode::HalfToEven)),
84 /// );
85 /// assert_eq!(D38::<12>::ZERO.checked_ln_strict_with(RoundingMode::HalfToEven), None);
86 /// ```
87 #[inline]
88 #[must_use]
89 pub fn checked_ln_strict_with(self, mode: RoundingMode) -> Option<Self> {
90 if self.0 <= Int::<N>::ZERO {
91 return None;
92 }
93 crate::policy::ln::checked_dispatch::<N, SCALE>(self.0, mode).map(Self)
94 }
95
96 /// Default-mode sibling of [`Self::checked_ln_strict_with`].
97 ///
98 /// ```
99 /// use decimal_scaled::D38;
100 /// assert!(D38::<12>::try_from(10i64).unwrap().checked_ln_strict().is_some());
101 /// assert_eq!(D38::<12>::try_from(-1i64).unwrap().checked_ln_strict(), None);
102 /// ```
103 #[inline]
104 #[must_use]
105 pub fn checked_ln_strict(self) -> Option<Self> {
106 self.checked_ln_strict_with(DEFAULT_ROUNDING_MODE)
107 }
108
109 /// Checked `log_strict_with`: logarithm in an arbitrary `base`,
110 /// `None` instead of a panic.
111 ///
112 /// Returns `None` when `self <= 0`, `base <= 0`, or `base == 1`
113 /// (the domain walls the default form panics on), or when the
114 /// result does not fit the storage range. Otherwise
115 /// `Some(self.log_strict_with(base, mode))`, bit-identical.
116 ///
117 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
118 /// out-of-range result still panics (wide kernel-shell seam not yet
119 /// reached). Domain errors return `None` at every tier.
120 ///
121 /// ```
122 /// use decimal_scaled::{D38, RoundingMode};
123 /// let eight = D38::<10>::try_from(8i64).unwrap();
124 /// let two = D38::<10>::try_from(2i64).unwrap();
125 /// assert!(eight.checked_log_strict_with(two, RoundingMode::HalfToEven).is_some());
126 /// assert_eq!(eight.checked_log_strict_with(D38::<10>::ONE, RoundingMode::HalfToEven), None);
127 /// ```
128 #[inline]
129 #[must_use]
130 pub fn checked_log_strict_with(self, base: Self, mode: RoundingMode) -> Option<Self> {
131 if self.0 <= Int::<N>::ZERO
132 || base.0 <= Int::<N>::ZERO
133 || base.0 == Self::unit_bits()
134 {
135 return None;
136 }
137 crate::policy::log::checked_dispatch::<N, SCALE>(self.0, base.0, mode).map(Self)
138 }
139
140 /// Default-mode sibling of [`Self::checked_log_strict_with`].
141 ///
142 /// ```
143 /// use decimal_scaled::D38;
144 /// let x = D38::<10>::try_from(100i64).unwrap();
145 /// assert!(x.checked_log_strict(D38::<10>::try_from(10i64).unwrap()).is_some());
146 /// assert_eq!(x.checked_log_strict(D38::<10>::ZERO), None);
147 /// ```
148 #[inline]
149 #[must_use]
150 pub fn checked_log_strict(self, base: Self) -> Option<Self> {
151 self.checked_log_strict_with(base, DEFAULT_ROUNDING_MODE)
152 }
153
154 /// Checked `log2_strict_with`: base-2 logarithm, `None` instead of
155 /// a panic.
156 ///
157 /// Returns `None` when `self <= 0`, or when the result does not fit
158 /// the storage range. Otherwise bit-identical `Some`.
159 ///
160 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
161 /// out-of-range result still panics (wide kernel-shell seam not yet reached).
162 ///
163 /// ```
164 /// use decimal_scaled::{D38, RoundingMode};
165 /// let eight = D38::<10>::try_from(8i64).unwrap();
166 /// assert_eq!(
167 /// eight.checked_log2_strict_with(RoundingMode::HalfToEven),
168 /// Some(eight.log2_strict_with(RoundingMode::HalfToEven)),
169 /// );
170 /// assert_eq!(D38::<10>::ZERO.checked_log2_strict_with(RoundingMode::HalfToEven), None);
171 /// ```
172 #[inline]
173 #[must_use]
174 pub fn checked_log2_strict_with(self, mode: RoundingMode) -> Option<Self> {
175 if self.0 <= Int::<N>::ZERO {
176 return None;
177 }
178 crate::policy::ln::checked_log2_dispatch::<N, SCALE>(self.0, mode).map(Self)
179 }
180
181 /// Default-mode sibling of [`Self::checked_log2_strict_with`].
182 ///
183 /// ```
184 /// use decimal_scaled::D38;
185 /// assert!(D38::<10>::try_from(4i64).unwrap().checked_log2_strict().is_some());
186 /// assert_eq!(D38::<10>::try_from(-4i64).unwrap().checked_log2_strict(), None);
187 /// ```
188 #[inline]
189 #[must_use]
190 pub fn checked_log2_strict(self) -> Option<Self> {
191 self.checked_log2_strict_with(DEFAULT_ROUNDING_MODE)
192 }
193
194 /// Checked `log10_strict_with`: base-10 logarithm, `None` instead
195 /// of a panic.
196 ///
197 /// Returns `None` when `self <= 0`, or when the result does not fit
198 /// the storage range. Otherwise bit-identical `Some`.
199 ///
200 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
201 /// out-of-range result still panics (wide kernel-shell seam not yet reached).
202 ///
203 /// ```
204 /// use decimal_scaled::{D38, RoundingMode};
205 /// let hundred = D38::<10>::try_from(100i64).unwrap();
206 /// assert!(hundred.checked_log10_strict_with(RoundingMode::HalfToEven).is_some());
207 /// assert_eq!(D38::<10>::ZERO.checked_log10_strict_with(RoundingMode::HalfToEven), None);
208 /// ```
209 #[inline]
210 #[must_use]
211 pub fn checked_log10_strict_with(self, mode: RoundingMode) -> Option<Self> {
212 if self.0 <= Int::<N>::ZERO {
213 return None;
214 }
215 crate::policy::ln::checked_log10_dispatch::<N, SCALE>(self.0, mode).map(Self)
216 }
217
218 /// Default-mode sibling of [`Self::checked_log10_strict_with`].
219 ///
220 /// ```
221 /// use decimal_scaled::D38;
222 /// assert!(D38::<10>::try_from(1000i64).unwrap().checked_log10_strict().is_some());
223 /// assert_eq!(D38::<10>::ZERO.checked_log10_strict(), None);
224 /// ```
225 #[inline]
226 #[must_use]
227 pub fn checked_log10_strict(self) -> Option<Self> {
228 self.checked_log10_strict_with(DEFAULT_ROUNDING_MODE)
229 }
230
231 /// Checked `log1p_strict_with`: `ln(1 + self)`, `None` instead of a
232 /// domain panic.
233 ///
234 /// Returns `None` when `self <= -1` — the domain wall the default
235 /// form panics on (`log1p: argument must be greater than -1`).
236 /// Otherwise `Some(self.log1p_strict_with(mode))`, bit-identical.
237 ///
238 /// There is no out-of-range case: `ln(1 + t)` is bounded by the
239 /// storage range wherever `1 + t` is representable, so every
240 /// in-domain argument returns `Some` at every tier. Unlike the
241 /// logarithms above, this needs no wide-tier caveat.
242 ///
243 /// ```
244 /// use decimal_scaled::{D38, RoundingMode};
245 /// let one = D38::<12>::ONE;
246 /// assert_eq!(
247 /// one.checked_log1p_strict_with(RoundingMode::HalfToEven),
248 /// Some(one.log1p_strict_with(RoundingMode::HalfToEven)),
249 /// );
250 /// // `t = -1` is the wall — `ln(0)` is undefined.
251 /// assert_eq!(
252 /// D38::<12>::try_from(-1i64).unwrap().checked_log1p_strict_with(RoundingMode::HalfToEven),
253 /// None,
254 /// );
255 /// ```
256 #[inline]
257 #[must_use]
258 pub fn checked_log1p_strict_with(self, mode: RoundingMode) -> Option<Self> {
259 if self.0 <= -Self::unit_bits() {
260 return None;
261 }
262 Some(Self(crate::policy::log1p::dispatch::<N, SCALE>(
263 self.0, mode,
264 )))
265 }
266
267 /// Default-mode sibling of [`Self::checked_log1p_strict_with`].
268 ///
269 /// ```
270 /// use decimal_scaled::D38;
271 /// assert!(D38::<12>::ONE.checked_log1p_strict().is_some());
272 /// assert_eq!(D38::<12>::try_from(-1i64).unwrap().checked_log1p_strict(), None);
273 /// assert_eq!(D38::<12>::try_from(-2i64).unwrap().checked_log1p_strict(), None);
274 /// ```
275 #[inline]
276 #[must_use]
277 pub fn checked_log1p_strict(self) -> Option<Self> {
278 self.checked_log1p_strict_with(DEFAULT_ROUNDING_MODE)
279 }
280
281 // ── Exponentials ──────────────────────────────────────────────
282
283 /// Checked `exp_strict_with`: `e^self`, `None` instead of a panic.
284 ///
285 /// `exp` has no domain wall; `None` means the correctly-rounded
286 /// result does not fit the storage range — the same condition on
287 /// which the default form panics. Otherwise
288 /// `Some(self.exp_strict_with(mode))`, bit-identical.
289 ///
290 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
291 /// out-of-range result still panics (kernel seam not yet reached).
292 ///
293 /// ```
294 /// use decimal_scaled::{D38, RoundingMode};
295 /// let one = D38::<12>::ONE;
296 /// assert_eq!(
297 /// one.checked_exp_strict_with(RoundingMode::HalfToEven),
298 /// Some(one.exp_strict_with(RoundingMode::HalfToEven)),
299 /// );
300 /// // e^120 has 53 integer digits — far outside D38's 38.
301 /// assert_eq!(D38::<12>::try_from(120i64).unwrap().checked_exp_strict_with(RoundingMode::HalfToEven), None);
302 /// ```
303 #[inline]
304 #[must_use]
305 pub fn checked_exp_strict_with(self, mode: RoundingMode) -> Option<Self> {
306 crate::policy::exp::checked_dispatch::<N, SCALE>(self.0, mode).map(Self)
307 }
308
309 /// Default-mode sibling of [`Self::checked_exp_strict_with`].
310 ///
311 /// ```
312 /// use decimal_scaled::D38;
313 /// assert!(D38::<12>::ONE.checked_exp_strict().is_some());
314 /// assert_eq!(D38::<12>::try_from(120i64).unwrap().checked_exp_strict(), None);
315 /// ```
316 #[inline]
317 #[must_use]
318 pub fn checked_exp_strict(self) -> Option<Self> {
319 self.checked_exp_strict_with(DEFAULT_ROUNDING_MODE)
320 }
321
322 /// Checked `exp2_strict_with`: `2^self`, `None` instead of a panic.
323 ///
324 /// No domain wall; `None` means the result does not fit the storage
325 /// range. Otherwise bit-identical `Some`.
326 ///
327 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
328 /// out-of-range result still panics (wide kernel-shell seam not yet reached).
329 ///
330 /// ```
331 /// use decimal_scaled::{D38, RoundingMode};
332 /// let ten = D38::<12>::try_from(10i64).unwrap();
333 /// assert_eq!(
334 /// ten.checked_exp2_strict_with(RoundingMode::HalfToEven),
335 /// Some(ten.exp2_strict_with(RoundingMode::HalfToEven)),
336 /// );
337 /// // 2^95 has 29 integer digits — outside D38<12>'s 26.
338 /// assert_eq!(D38::<12>::try_from(95i64).unwrap().checked_exp2_strict_with(RoundingMode::HalfToEven), None);
339 /// ```
340 #[inline]
341 #[must_use]
342 pub fn checked_exp2_strict_with(self, mode: RoundingMode) -> Option<Self> {
343 crate::policy::exp::checked_exp2_dispatch::<N, SCALE>(self.0, mode).map(Self)
344 }
345
346 /// Default-mode sibling of [`Self::checked_exp2_strict_with`].
347 ///
348 /// ```
349 /// use decimal_scaled::D38;
350 /// assert!(D38::<12>::try_from(10i64).unwrap().checked_exp2_strict().is_some());
351 /// assert_eq!(D38::<12>::try_from(95i64).unwrap().checked_exp2_strict(), None);
352 /// ```
353 #[inline]
354 #[must_use]
355 pub fn checked_exp2_strict(self) -> Option<Self> {
356 self.checked_exp2_strict_with(DEFAULT_ROUNDING_MODE)
357 }
358
359 /// Checked `expm1_strict_with`: `e^self − 1`, `None` instead of a
360 /// panic.
361 ///
362 /// `expm1` is total over its argument, so there is no domain wall
363 /// and `None` is reserved for a result that does not fit the storage
364 /// range — the same condition on which the default form panics.
365 /// Otherwise `Some(self.expm1_strict_with(mode))`, bit-identical.
366 ///
367 /// Out-of-range detection: the kernel seam that reports overflow as
368 /// an `Option` has not been threaded through `expm1`, so at every
369 /// tier an out-of-range result still PANICS rather than returning
370 /// `None`, and this form is `Some` whenever the default form
371 /// returns at all. That is the same gap the wide-tier note on
372 /// [`Self::checked_exp_strict_with`] records — `exp` has the seam on
373 /// D18/D38, `expm1` does not have it yet — so this pair completes
374 /// the documented surface rather than adding a stronger guarantee.
375 ///
376 /// ```
377 /// use decimal_scaled::{D38, RoundingMode};
378 /// let one = D38::<12>::ONE;
379 /// assert_eq!(
380 /// one.checked_expm1_strict_with(RoundingMode::HalfToEven),
381 /// Some(one.expm1_strict_with(RoundingMode::HalfToEven)),
382 /// );
383 /// ```
384 #[inline]
385 #[must_use]
386 pub fn checked_expm1_strict_with(self, mode: RoundingMode) -> Option<Self> {
387 Some(Self(crate::policy::expm1::dispatch::<N, SCALE>(
388 self.0, mode,
389 )))
390 }
391
392 /// Default-mode sibling of [`Self::checked_expm1_strict_with`].
393 ///
394 /// ```
395 /// use decimal_scaled::D38;
396 /// assert!(D38::<12>::ONE.checked_expm1_strict().is_some());
397 /// assert!(D38::<12>::try_from(-1i64).unwrap().checked_expm1_strict().is_some());
398 /// ```
399 #[inline]
400 #[must_use]
401 pub fn checked_expm1_strict(self) -> Option<Self> {
402 self.checked_expm1_strict_with(DEFAULT_ROUNDING_MODE)
403 }
404
405 // ── Power ─────────────────────────────────────────────────────
406
407 /// Checked `powf_strict_with`: `self^exp`, `None` instead of a
408 /// panic.
409 ///
410 /// `powf` has no domain panic: a non-positive base saturates to
411 /// zero (the kernel's documented behaviour at every tier), so
412 /// `checked_powf` returns `Some(ZERO)` there, matching the default
413 /// form. `None` means the result does not fit the storage range.
414 ///
415 /// Out-of-range detection: exact on D18/D38; on the wide tiers an
416 /// out-of-range result still panics (wide kernel-shell seam not yet reached).
417 ///
418 /// ```
419 /// use decimal_scaled::{D38, RoundingMode};
420 /// let three = D38::<10>::try_from(3i64).unwrap();
421 /// let two = D38::<10>::try_from(2i64).unwrap();
422 /// assert_eq!(
423 /// three.checked_powf_strict_with(two, RoundingMode::HalfToEven),
424 /// Some(three.powf_strict_with(two, RoundingMode::HalfToEven)),
425 /// );
426 /// // Non-positive base saturates to zero, as the default form does.
427 /// let half = D38::<10>::ONE / two;
428 /// assert_eq!(
429 /// (-three).checked_powf_strict_with(half, RoundingMode::HalfToEven),
430 /// Some(D38::<10>::ZERO),
431 /// );
432 /// // 10^30 has 31 integer digits — out of D38<10>'s 28.
433 /// let ten = D38::<10>::try_from(10i64).unwrap();
434 /// let thirty = D38::<10>::try_from(30i64).unwrap();
435 /// assert_eq!(ten.checked_powf_strict_with(thirty, RoundingMode::HalfToEven), None);
436 /// ```
437 #[inline]
438 #[must_use]
439 pub fn checked_powf_strict_with(self, exp: Self, mode: RoundingMode) -> Option<Self> {
440 crate::policy::pow::checked_dispatch::<N, SCALE>(self.0, exp.0, mode).map(Self)
441 }
442
443 /// Default-mode sibling of [`Self::checked_powf_strict_with`].
444 ///
445 /// ```
446 /// use decimal_scaled::D38;
447 /// let x = D38::<10>::try_from(2i64).unwrap();
448 /// assert!(x.checked_powf_strict(D38::<10>::try_from(8i64).unwrap()).is_some());
449 /// ```
450 #[inline]
451 #[must_use]
452 pub fn checked_powf_strict(self, exp: Self) -> Option<Self> {
453 self.checked_powf_strict_with(exp, DEFAULT_ROUNDING_MODE)
454 }
455
456 // ── Roots ─────────────────────────────────────────────────────
457
458 /// Checked `sqrt_strict_with`. Always `Some`: the strict square
459 /// root is total — negative inputs saturate to zero (the policy's
460 /// documented behaviour, not a panic), and the result `√v ≤
461 /// max(v, 1)` always fits the storage range. The checked form
462 /// exists for surface uniformity.
463 ///
464 /// ```
465 /// use decimal_scaled::{D38, RoundingMode};
466 /// let nine = D38::<10>::try_from(9i64).unwrap();
467 /// assert_eq!(
468 /// nine.checked_sqrt_strict_with(RoundingMode::HalfToEven),
469 /// Some(nine.sqrt_strict_with(RoundingMode::HalfToEven)),
470 /// );
471 /// ```
472 #[inline]
473 #[must_use]
474 pub fn checked_sqrt_strict_with(self, mode: RoundingMode) -> Option<Self>
475 where
476 Limbs<N>: ComputeLimbs,
477 {
478 Some(Self(crate::policy::sqrt::dispatch::<N, SCALE>(self.0, mode)))
479 }
480
481 /// Default-mode sibling of [`Self::checked_sqrt_strict_with`].
482 ///
483 /// ```
484 /// use decimal_scaled::D38;
485 /// assert!(D38::<10>::try_from(2i64).unwrap().checked_sqrt_strict().is_some());
486 /// ```
487 #[inline]
488 #[must_use]
489 pub fn checked_sqrt_strict(self) -> Option<Self>
490 where
491 Limbs<N>: ComputeLimbs,
492 {
493 self.checked_sqrt_strict_with(DEFAULT_ROUNDING_MODE)
494 }
495
496 /// Checked `cbrt_strict_with`. Always `Some`: the cube root is
497 /// total over the signed domain and `∛v` never exceeds `max(|v|,
498 /// 1)`, so it always fits the storage range.
499 ///
500 /// ```
501 /// use decimal_scaled::{D38, RoundingMode};
502 /// let x = D38::<10>::try_from(-27i64).unwrap();
503 /// assert_eq!(
504 /// x.checked_cbrt_strict_with(RoundingMode::HalfToEven),
505 /// Some(x.cbrt_strict_with(RoundingMode::HalfToEven)),
506 /// );
507 /// ```
508 #[inline]
509 #[must_use]
510 pub fn checked_cbrt_strict_with(self, mode: RoundingMode) -> Option<Self>
511 where
512 Limbs<N>: ComputeLimbs,
513 {
514 Some(Self(crate::policy::cbrt::dispatch::<N, SCALE>(self.0, mode)))
515 }
516
517 /// Default-mode sibling of [`Self::checked_cbrt_strict_with`].
518 ///
519 /// ```
520 /// use decimal_scaled::D38;
521 /// assert!(D38::<10>::try_from(8i64).unwrap().checked_cbrt_strict().is_some());
522 /// ```
523 #[inline]
524 #[must_use]
525 pub fn checked_cbrt_strict(self) -> Option<Self>
526 where
527 Limbs<N>: ComputeLimbs,
528 {
529 self.checked_cbrt_strict_with(DEFAULT_ROUNDING_MODE)
530 }
531
532 /// Checked `hypot_strict_with`: `√(self² + other²)`, `None` instead
533 /// of a panic.
534 ///
535 /// No domain wall; `None` means the result does not fit the storage
536 /// range (possible only when both operands are near the range
537 /// limit). Otherwise bit-identical `Some`.
538 ///
539 /// ```
540 /// use decimal_scaled::{D38, RoundingMode};
541 /// let three = D38::<10>::try_from(3i64).unwrap();
542 /// let four = D38::<10>::try_from(4i64).unwrap();
543 /// assert_eq!(
544 /// three.checked_hypot_strict_with(four, RoundingMode::HalfToEven),
545 /// Some(three.hypot_strict_with(four, RoundingMode::HalfToEven)),
546 /// );
547 /// assert_eq!(D38::<10>::MAX.checked_hypot_strict_with(D38::<10>::MAX, RoundingMode::HalfToEven), None);
548 /// ```
549 #[inline]
550 #[must_use]
551 pub fn checked_hypot_strict_with(self, other: Self, mode: RoundingMode) -> Option<Self>
552 where
553 Limbs<N>: ComputeLimbs,
554 {
555 crate::policy::hypot::checked_dispatch::<N, SCALE>(self.0, other.0, mode).map(Self)
556 }
557
558 /// Default-mode sibling of [`Self::checked_hypot_strict_with`].
559 ///
560 /// ```
561 /// use decimal_scaled::D38;
562 /// let a = D38::<10>::try_from(5i64).unwrap();
563 /// assert!(a.checked_hypot_strict(a).is_some());
564 /// ```
565 #[inline]
566 #[must_use]
567 pub fn checked_hypot_strict(self, other: Self) -> Option<Self>
568 where
569 Limbs<N>: ComputeLimbs,
570 {
571 self.checked_hypot_strict_with(other, DEFAULT_ROUNDING_MODE)
572 }
573
574 // ── Trigonometry (forward) ────────────────────────────────────
575
576 /// Checked `sin_strict_with`. Always `Some`: `sin` is total and
577 /// `|sin x| <= 1`, which fits every tier's range at every valid
578 /// scale (each tier keeps >= ~10 of integer headroom at its
579 /// maximum scale).
580 ///
581 /// ```
582 /// use decimal_scaled::{D38, RoundingMode};
583 /// let one = D38::<12>::ONE;
584 /// assert_eq!(
585 /// one.checked_sin_strict_with(RoundingMode::HalfToEven),
586 /// Some(one.sin_strict_with(RoundingMode::HalfToEven)),
587 /// );
588 /// ```
589 #[inline]
590 #[must_use]
591 pub fn checked_sin_strict_with(self, mode: RoundingMode) -> Option<Self> {
592 Some(Self(crate::policy::trig::sin_dispatch::<N, SCALE>(self.0, mode)))
593 }
594
595 /// Default-mode sibling of [`Self::checked_sin_strict_with`].
596 ///
597 /// ```
598 /// use decimal_scaled::D38;
599 /// assert!(D38::<12>::ONE.checked_sin_strict().is_some());
600 /// ```
601 #[inline]
602 #[must_use]
603 pub fn checked_sin_strict(self) -> Option<Self> {
604 self.checked_sin_strict_with(DEFAULT_ROUNDING_MODE)
605 }
606
607 /// Checked `cos_strict_with`. Always `Some`: `cos` is total and
608 /// `|cos x| <= 1` fits every tier's range at every valid scale.
609 ///
610 /// ```
611 /// use decimal_scaled::{D38, RoundingMode};
612 /// let one = D38::<12>::ONE;
613 /// assert_eq!(
614 /// one.checked_cos_strict_with(RoundingMode::HalfToEven),
615 /// Some(one.cos_strict_with(RoundingMode::HalfToEven)),
616 /// );
617 /// ```
618 #[inline]
619 #[must_use]
620 pub fn checked_cos_strict_with(self, mode: RoundingMode) -> Option<Self> {
621 Some(Self(crate::policy::trig::cos_dispatch::<N, SCALE>(self.0, mode)))
622 }
623
624 /// Default-mode sibling of [`Self::checked_cos_strict_with`].
625 ///
626 /// ```
627 /// use decimal_scaled::D38;
628 /// assert!(D38::<12>::ONE.checked_cos_strict().is_some());
629 /// ```
630 #[inline]
631 #[must_use]
632 pub fn checked_cos_strict(self) -> Option<Self> {
633 self.checked_cos_strict_with(DEFAULT_ROUNDING_MODE)
634 }
635
636 /// Checked `tan_strict_with`: `None` instead of a panic.
637 ///
638 /// The default form panics when the argument's cosine rounds to
639 /// zero at the working precision (an odd multiple of π/2 to within
640 /// the kernel's resolution) and when the result does not fit the
641 /// storage range (near those asymptotes). Both conditions are
642 /// detected inside the kernels at every tier; this checked form
643 /// currently panics on them identically to the default form
644 /// (kernel seam not yet reached). For every other input
645 /// it returns bit-identical `Some`.
646 ///
647 /// ```
648 /// use decimal_scaled::{D38, RoundingMode};
649 /// let one = D38::<12>::ONE;
650 /// assert_eq!(
651 /// one.checked_tan_strict_with(RoundingMode::HalfToEven),
652 /// Some(one.tan_strict_with(RoundingMode::HalfToEven)),
653 /// );
654 /// ```
655 #[inline]
656 #[must_use]
657 pub fn checked_tan_strict_with(self, mode: RoundingMode) -> Option<Self> {
658 Some(Self(crate::policy::trig::tan_dispatch::<N, SCALE>(self.0, mode)))
659 }
660
661 /// Default-mode sibling of [`Self::checked_tan_strict_with`].
662 ///
663 /// ```
664 /// use decimal_scaled::D38;
665 /// assert!(D38::<12>::ONE.checked_tan_strict().is_some());
666 /// ```
667 #[inline]
668 #[must_use]
669 pub fn checked_tan_strict(self) -> Option<Self> {
670 self.checked_tan_strict_with(DEFAULT_ROUNDING_MODE)
671 }
672
673 // ── Trigonometry (inverse) ────────────────────────────────────
674
675 /// Checked `asin_strict_with`: `None` instead of a domain panic.
676 ///
677 /// Returns `None` when `|self| > 1` (the default form's domain
678 /// wall). The result `|asin x| <= π/2` always fits the storage
679 /// range, so there is no out-of-range case. Otherwise bit-identical
680 /// `Some`.
681 ///
682 /// ```
683 /// use decimal_scaled::{D38, RoundingMode};
684 /// let half = D38::<12>::ONE / D38::<12>::try_from(2i64).unwrap();
685 /// assert_eq!(
686 /// half.checked_asin_strict_with(RoundingMode::HalfToEven),
687 /// Some(half.asin_strict_with(RoundingMode::HalfToEven)),
688 /// );
689 /// assert_eq!(D38::<12>::try_from(2i64).unwrap().checked_asin_strict_with(RoundingMode::HalfToEven), None);
690 /// ```
691 #[inline]
692 #[must_use]
693 pub fn checked_asin_strict_with(self, mode: RoundingMode) -> Option<Self> {
694 let one = Self::unit_bits();
695 if self.0 > one || self.0 < -one {
696 return None;
697 }
698 crate::policy::trig::checked_asin_dispatch::<N, SCALE>(self.0, mode).map(Self)
699 }
700
701 /// Default-mode sibling of [`Self::checked_asin_strict_with`].
702 ///
703 /// ```
704 /// use decimal_scaled::D38;
705 /// assert!(D38::<12>::ONE.checked_asin_strict().is_some());
706 /// assert_eq!(D38::<12>::try_from(-2i64).unwrap().checked_asin_strict(), None);
707 /// ```
708 #[inline]
709 #[must_use]
710 pub fn checked_asin_strict(self) -> Option<Self> {
711 self.checked_asin_strict_with(DEFAULT_ROUNDING_MODE)
712 }
713
714 /// Checked `acos_strict_with`: `None` instead of a domain panic.
715 ///
716 /// Returns `None` when `|self| > 1`. The result `0 <= acos x <= π`
717 /// always fits the storage range, so there is no out-of-range case.
718 /// Otherwise bit-identical `Some`.
719 ///
720 /// ```
721 /// use decimal_scaled::{D38, RoundingMode};
722 /// let half = D38::<12>::ONE / D38::<12>::try_from(2i64).unwrap();
723 /// assert_eq!(
724 /// half.checked_acos_strict_with(RoundingMode::HalfToEven),
725 /// Some(half.acos_strict_with(RoundingMode::HalfToEven)),
726 /// );
727 /// assert_eq!(D38::<12>::try_from(2i64).unwrap().checked_acos_strict_with(RoundingMode::HalfToEven), None);
728 /// ```
729 #[inline]
730 #[must_use]
731 pub fn checked_acos_strict_with(self, mode: RoundingMode) -> Option<Self> {
732 let one = Self::unit_bits();
733 if self.0 > one || self.0 < -one {
734 return None;
735 }
736 crate::policy::trig::checked_acos_dispatch::<N, SCALE>(self.0, mode).map(Self)
737 }
738
739 /// Default-mode sibling of [`Self::checked_acos_strict_with`].
740 ///
741 /// ```
742 /// use decimal_scaled::D38;
743 /// assert!(D38::<12>::ONE.checked_acos_strict().is_some());
744 /// assert_eq!(D38::<12>::try_from(2i64).unwrap().checked_acos_strict(), None);
745 /// ```
746 #[inline]
747 #[must_use]
748 pub fn checked_acos_strict(self) -> Option<Self> {
749 self.checked_acos_strict_with(DEFAULT_ROUNDING_MODE)
750 }
751
752 /// Checked `atan_strict_with`. Always `Some`: `atan` is total and
753 /// `|atan x| < π/2` fits every tier's range at every valid scale.
754 ///
755 /// ```
756 /// use decimal_scaled::{D38, RoundingMode};
757 /// let x = D38::<12>::try_from(5i64).unwrap();
758 /// assert_eq!(
759 /// x.checked_atan_strict_with(RoundingMode::HalfToEven),
760 /// Some(x.atan_strict_with(RoundingMode::HalfToEven)),
761 /// );
762 /// ```
763 #[inline]
764 #[must_use]
765 pub fn checked_atan_strict_with(self, mode: RoundingMode) -> Option<Self> {
766 Some(Self(crate::policy::trig::atan_dispatch::<N, SCALE>(self.0, mode)))
767 }
768
769 /// Default-mode sibling of [`Self::checked_atan_strict_with`].
770 ///
771 /// ```
772 /// use decimal_scaled::D38;
773 /// assert!(D38::<12>::try_from(3i64).unwrap().checked_atan_strict().is_some());
774 /// ```
775 #[inline]
776 #[must_use]
777 pub fn checked_atan_strict(self) -> Option<Self> {
778 self.checked_atan_strict_with(DEFAULT_ROUNDING_MODE)
779 }
780
781 /// Checked `atan2_strict_with`. Always `Some`: `atan2` is total
782 /// (including the `(0, 0)` origin, which yields `0`) and `|atan2(y,
783 /// x)| <= π` fits every tier's range at every valid scale.
784 ///
785 /// ```
786 /// use decimal_scaled::{D38, RoundingMode};
787 /// let y = D38::<12>::ONE;
788 /// let x = D38::<12>::try_from(2i64).unwrap();
789 /// assert_eq!(
790 /// y.checked_atan2_strict_with(x, RoundingMode::HalfToEven),
791 /// Some(y.atan2_strict_with(x, RoundingMode::HalfToEven)),
792 /// );
793 /// ```
794 #[inline]
795 #[must_use]
796 pub fn checked_atan2_strict_with(self, other: Self, mode: RoundingMode) -> Option<Self> {
797 crate::policy::trig::checked_atan2_dispatch::<N, SCALE>(self.0, other.0, mode)
798 .map(Self)
799 }
800
801 /// Default-mode sibling of [`Self::checked_atan2_strict_with`].
802 ///
803 /// ```
804 /// use decimal_scaled::D38;
805 /// assert!(D38::<12>::ONE.checked_atan2_strict(D38::<12>::ONE).is_some());
806 /// ```
807 #[inline]
808 #[must_use]
809 pub fn checked_atan2_strict(self, other: Self) -> Option<Self> {
810 self.checked_atan2_strict_with(other, DEFAULT_ROUNDING_MODE)
811 }
812
813 // ── Hyperbolics ───────────────────────────────────────────────
814
815 /// Checked `sinh_strict_with`: `None` instead of a panic.
816 ///
817 /// `sinh` has no domain wall; `None` means the result does not fit
818 /// the storage range (it grows like `e^|x|/2`). Otherwise
819 /// bit-identical `Some`.
820 ///
821 /// Out-of-range detection: exact on D18 (a result that fits the
822 /// D38 work width but not D18 storage is `None`); detection deeper
823 /// in the kernels (D38 and the wide tiers) still panics (kernel seam not yet reached).
824 ///
825 /// ```
826 /// use decimal_scaled::{D18, D38, RoundingMode};
827 /// let one = D38::<12>::ONE;
828 /// assert_eq!(
829 /// one.checked_sinh_strict_with(RoundingMode::HalfToEven),
830 /// Some(one.sinh_strict_with(RoundingMode::HalfToEven)),
831 /// );
832 /// // sinh(40) ~ 1.2e17 exceeds D18<6>'s range but fits the D38 work width.
833 /// assert_eq!(D18::<6>::try_from(40).unwrap().checked_sinh_strict_with(RoundingMode::HalfToEven), None);
834 /// ```
835 #[inline]
836 #[must_use]
837 pub fn checked_sinh_strict_with(self, mode: RoundingMode) -> Option<Self> {
838 crate::policy::trig::checked_sinh_dispatch::<N, SCALE>(self.0, mode)
839 .map(Self)
840 }
841
842 /// Default-mode sibling of [`Self::checked_sinh_strict_with`].
843 ///
844 /// ```
845 /// use decimal_scaled::{D18, D38};
846 /// assert!(D38::<12>::ONE.checked_sinh_strict().is_some());
847 /// assert_eq!(D18::<6>::try_from(40).unwrap().checked_sinh_strict(), None);
848 /// ```
849 #[inline]
850 #[must_use]
851 pub fn checked_sinh_strict(self) -> Option<Self> {
852 self.checked_sinh_strict_with(DEFAULT_ROUNDING_MODE)
853 }
854
855 /// Checked `cosh_strict_with`: `None` instead of a panic.
856 ///
857 /// `cosh` has no domain wall; `None` means the result does not fit
858 /// the storage range. Otherwise bit-identical `Some`.
859 ///
860 /// Out-of-range detection: exact on D18 (a result that fits the
861 /// D38 work width but not D18 storage is `None`); detection deeper
862 /// in the kernels (D38 and the wide tiers) still panics (kernel seam not yet reached).
863 ///
864 /// ```
865 /// use decimal_scaled::{D18, D38, RoundingMode};
866 /// let one = D38::<12>::ONE;
867 /// assert_eq!(
868 /// one.checked_cosh_strict_with(RoundingMode::HalfToEven),
869 /// Some(one.cosh_strict_with(RoundingMode::HalfToEven)),
870 /// );
871 /// // cosh(40) ~ 1.2e17 exceeds D18<6>'s range but fits the D38 work width.
872 /// assert_eq!(D18::<6>::try_from(40).unwrap().checked_cosh_strict_with(RoundingMode::HalfToEven), None);
873 /// ```
874 #[inline]
875 #[must_use]
876 pub fn checked_cosh_strict_with(self, mode: RoundingMode) -> Option<Self> {
877 crate::policy::trig::checked_cosh_dispatch::<N, SCALE>(self.0, mode)
878 .map(Self)
879 }
880
881 /// Default-mode sibling of [`Self::checked_cosh_strict_with`].
882 ///
883 /// ```
884 /// use decimal_scaled::{D18, D38};
885 /// assert!(D38::<12>::ONE.checked_cosh_strict().is_some());
886 /// assert_eq!(D18::<6>::try_from(40).unwrap().checked_cosh_strict(), None);
887 /// ```
888 #[inline]
889 #[must_use]
890 pub fn checked_cosh_strict(self) -> Option<Self> {
891 self.checked_cosh_strict_with(DEFAULT_ROUNDING_MODE)
892 }
893
894 /// Checked `tanh_strict_with`. Always `Some`: `tanh` is total and
895 /// `|tanh x| <= 1` fits every tier's range at every valid scale.
896 ///
897 /// ```
898 /// use decimal_scaled::{D38, RoundingMode};
899 /// let one = D38::<12>::ONE;
900 /// assert_eq!(
901 /// one.checked_tanh_strict_with(RoundingMode::HalfToEven),
902 /// Some(one.tanh_strict_with(RoundingMode::HalfToEven)),
903 /// );
904 /// ```
905 #[inline]
906 #[must_use]
907 pub fn checked_tanh_strict_with(self, mode: RoundingMode) -> Option<Self> {
908 crate::policy::trig::checked_tanh_dispatch::<N, SCALE>(self.0, mode).map(Self)
909 }
910
911 /// Default-mode sibling of [`Self::checked_tanh_strict_with`].
912 ///
913 /// ```
914 /// use decimal_scaled::D38;
915 /// assert!(D38::<12>::ONE.checked_tanh_strict().is_some());
916 /// ```
917 #[inline]
918 #[must_use]
919 pub fn checked_tanh_strict(self) -> Option<Self> {
920 self.checked_tanh_strict_with(DEFAULT_ROUNDING_MODE)
921 }
922
923 /// Checked `asinh_strict_with`. Always `Some`: `asinh` is total and
924 /// `|asinh x| <= max(|x|, 1)` always fits the storage range when
925 /// `x` does.
926 ///
927 /// ```
928 /// use decimal_scaled::{D38, RoundingMode};
929 /// let x = D38::<12>::try_from(3i64).unwrap();
930 /// assert_eq!(
931 /// x.checked_asinh_strict_with(RoundingMode::HalfToEven),
932 /// Some(x.asinh_strict_with(RoundingMode::HalfToEven)),
933 /// );
934 /// ```
935 #[inline]
936 #[must_use]
937 pub fn checked_asinh_strict_with(self, mode: RoundingMode) -> Option<Self> {
938 crate::policy::trig::checked_asinh_dispatch::<N, SCALE>(self.0, mode).map(Self)
939 }
940
941 /// Default-mode sibling of [`Self::checked_asinh_strict_with`].
942 ///
943 /// ```
944 /// use decimal_scaled::D38;
945 /// assert!(D38::<12>::try_from(2i64).unwrap().checked_asinh_strict().is_some());
946 /// ```
947 #[inline]
948 #[must_use]
949 pub fn checked_asinh_strict(self) -> Option<Self> {
950 self.checked_asinh_strict_with(DEFAULT_ROUNDING_MODE)
951 }
952
953 /// Checked `acosh_strict_with`: `None` instead of a domain panic.
954 ///
955 /// Returns `None` when `self < 1` (the default form's domain wall).
956 /// The result `acosh x < ln(2x) <= x` always fits the storage range
957 /// when `x` does. Otherwise bit-identical `Some`.
958 ///
959 /// ```
960 /// use decimal_scaled::{D38, RoundingMode};
961 /// let two = D38::<12>::try_from(2i64).unwrap();
962 /// assert_eq!(
963 /// two.checked_acosh_strict_with(RoundingMode::HalfToEven),
964 /// Some(two.acosh_strict_with(RoundingMode::HalfToEven)),
965 /// );
966 /// assert_eq!(D38::<12>::ZERO.checked_acosh_strict_with(RoundingMode::HalfToEven), None);
967 /// ```
968 #[inline]
969 #[must_use]
970 pub fn checked_acosh_strict_with(self, mode: RoundingMode) -> Option<Self> {
971 if self.0 < Self::unit_bits() {
972 return None;
973 }
974 crate::policy::trig::checked_acosh_dispatch::<N, SCALE>(self.0, mode).map(Self)
975 }
976
977 /// Default-mode sibling of [`Self::checked_acosh_strict_with`].
978 ///
979 /// ```
980 /// use decimal_scaled::D38;
981 /// assert!(D38::<12>::try_from(3i64).unwrap().checked_acosh_strict().is_some());
982 /// assert_eq!(D38::<12>::ZERO.checked_acosh_strict(), None);
983 /// ```
984 #[inline]
985 #[must_use]
986 pub fn checked_acosh_strict(self) -> Option<Self> {
987 self.checked_acosh_strict_with(DEFAULT_ROUNDING_MODE)
988 }
989
990 /// Checked `atanh_strict_with`: `None` instead of a panic.
991 ///
992 /// Returns `None` when `|self| >= 1` (the default form's domain
993 /// wall — `atanh` diverges at ±1). An out-of-range result (the
994 /// logarithmic blow-up just inside ±1 at a near-maximum scale) is
995 /// `None` on D18 when it fits the D38 work width; detection deeper
996 /// in the kernels still panics, identically to the default form
997 /// (kernel seam not yet reached). Otherwise bit-identical
998 /// `Some`.
999 ///
1000 /// ```
1001 /// use decimal_scaled::{D38, RoundingMode};
1002 /// let half = D38::<12>::ONE / D38::<12>::try_from(2i64).unwrap();
1003 /// assert_eq!(
1004 /// half.checked_atanh_strict_with(RoundingMode::HalfToEven),
1005 /// Some(half.atanh_strict_with(RoundingMode::HalfToEven)),
1006 /// );
1007 /// assert_eq!(D38::<12>::ONE.checked_atanh_strict_with(RoundingMode::HalfToEven), None);
1008 /// ```
1009 #[inline]
1010 #[must_use]
1011 pub fn checked_atanh_strict_with(self, mode: RoundingMode) -> Option<Self> {
1012 let one = Self::unit_bits();
1013 if self.0 >= one || self.0 <= -one {
1014 return None;
1015 }
1016 crate::policy::trig::checked_atanh_dispatch::<N, SCALE>(self.0, mode)
1017 .map(Self)
1018 }
1019
1020 /// Default-mode sibling of [`Self::checked_atanh_strict_with`].
1021 ///
1022 /// ```
1023 /// use decimal_scaled::D38;
1024 /// let half = D38::<12>::ONE / D38::<12>::try_from(2i64).unwrap();
1025 /// assert!(half.checked_atanh_strict().is_some());
1026 /// assert_eq!(D38::<12>::ONE.checked_atanh_strict(), None);
1027 /// ```
1028 #[inline]
1029 #[must_use]
1030 pub fn checked_atanh_strict(self) -> Option<Self> {
1031 self.checked_atanh_strict_with(DEFAULT_ROUNDING_MODE)
1032 }
1033
1034 // ── Angle conversion ──────────────────────────────────────────
1035
1036 /// Checked `to_degrees_strict_with`: `None` instead of a panic.
1037 ///
1038 /// No domain wall; `None` means `self · (180/π)` does not fit the
1039 /// storage range (the result is ~57.3× the input). Otherwise
1040 /// bit-identical `Some`.
1041 ///
1042 /// Out-of-range detection: exact on D18 (a result that fits the
1043 /// D38 work width but not D18 storage is `None`); detection deeper
1044 /// in the kernels (D38 and the wide tiers) still panics (kernel seam not yet reached).
1045 ///
1046 /// ```
1047 /// use decimal_scaled::{D18, D38, RoundingMode};
1048 /// let one = D38::<12>::ONE;
1049 /// assert_eq!(
1050 /// one.checked_to_degrees_strict_with(RoundingMode::HalfToEven),
1051 /// Some(one.to_degrees_strict_with(RoundingMode::HalfToEven)),
1052 /// );
1053 /// // MAX·(180/π) leaves D18's range but fits the D38 work width.
1054 /// assert_eq!(D18::<6>::MAX.checked_to_degrees_strict_with(RoundingMode::HalfToEven), None);
1055 /// ```
1056 #[inline]
1057 #[must_use]
1058 pub fn checked_to_degrees_strict_with(self, mode: RoundingMode) -> Option<Self> {
1059 crate::policy::trig::checked_to_degrees_dispatch::<N, SCALE>(self.0, mode)
1060 .map(Self)
1061 }
1062
1063 /// Default-mode sibling of [`Self::checked_to_degrees_strict_with`].
1064 ///
1065 /// ```
1066 /// use decimal_scaled::{D18, D38};
1067 /// assert!(D38::<12>::ONE.checked_to_degrees_strict().is_some());
1068 /// assert_eq!(D18::<6>::MAX.checked_to_degrees_strict(), None);
1069 /// ```
1070 #[inline]
1071 #[must_use]
1072 pub fn checked_to_degrees_strict(self) -> Option<Self> {
1073 self.checked_to_degrees_strict_with(DEFAULT_ROUNDING_MODE)
1074 }
1075
1076 /// Checked `to_radians_strict_with`. Always `Some`: the conversion
1077 /// multiplies by `π/180 < 1`, so the result is strictly smaller in
1078 /// magnitude than the (representable) input.
1079 ///
1080 /// ```
1081 /// use decimal_scaled::{D38, RoundingMode};
1082 /// let x = D38::<12>::try_from(180i64).unwrap();
1083 /// assert_eq!(
1084 /// x.checked_to_radians_strict_with(RoundingMode::HalfToEven),
1085 /// Some(x.to_radians_strict_with(RoundingMode::HalfToEven)),
1086 /// );
1087 /// ```
1088 #[inline]
1089 #[must_use]
1090 pub fn checked_to_radians_strict_with(self, mode: RoundingMode) -> Option<Self> {
1091 crate::policy::trig::checked_to_radians_dispatch::<N, SCALE>(self.0, mode)
1092 .map(Self)
1093 }
1094
1095 /// Default-mode sibling of [`Self::checked_to_radians_strict_with`].
1096 ///
1097 /// ```
1098 /// use decimal_scaled::D38;
1099 /// assert!(D38::<12>::try_from(90i64).unwrap().checked_to_radians_strict().is_some());
1100 /// ```
1101 #[inline]
1102 #[must_use]
1103 pub fn checked_to_radians_strict(self) -> Option<Self> {
1104 self.checked_to_radians_strict_with(DEFAULT_ROUNDING_MODE)
1105 }
1106}