Skip to main content

decimal_scaled/types/
trig_fast.rs

1// SPDX-FileCopyrightText: 2026 John Moxley
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Lossy (f64-bridge) trig methods for D38.
5//!
6//! Companion to `types/trig.rs`. The plain methods here are the
7//! f64-bridge variants, gated on std + (no strict feature or
8//! fast set). When strict is on, the dispatcher in the
9//! _strict file shadows these.
10
11
12impl<const SCALE: u32> crate::D<crate::int::types::Int<2>, SCALE> {
13    // ── Forward trig (radians input) ──────────────────────────────────
14
15    /// Sine of `self`, where `self` is in radians.
16    ///
17    /// # Precision
18    ///
19    /// Lossy: involves f64 at some point; result may lose precision.
20    ///
21    /// # Examples
22    ///
23    /// ```ignore
24    /// # #[cfg(feature = "std")]
25    /// # {
26    /// use decimal_scaled::D38s12;
27    /// // sin(0) == 0 (bit-exact: f64::sin(0.0) == 0.0).
28    /// assert_eq!(D38s12::ZERO.sin(), D38s12::ZERO);
29    /// # }
30    /// ```
31    #[cfg(feature = "std")]
32    #[inline]
33    #[must_use]
34    pub fn sin_fast(self) -> Self {
35        Self::from_f64(self.to_f64().sin())
36    }
37
38    /// Cosine of `self`, where `self` is in radians.
39    ///
40    /// # Precision
41    ///
42    /// Lossy: involves f64 at some point; result may lose precision.
43    ///
44    /// # Examples
45    ///
46    /// ```ignore
47    /// # #[cfg(feature = "std")]
48    /// # {
49    /// use decimal_scaled::D38s12;
50    /// // cos(0) == 1 (bit-exact: f64::cos(0.0) == 1.0).
51    /// assert_eq!(D38s12::ZERO.cos(), D38s12::ONE);
52    /// # }
53    /// ```
54    #[cfg(feature = "std")]
55    #[inline]
56    #[must_use]
57    pub fn cos_fast(self) -> Self {
58        Self::from_f64(self.to_f64().cos())
59    }
60
61    /// Tangent of `self`, where `self` is in radians.
62    ///
63    /// `f64::tan` returns very large magnitudes near odd multiples of
64    /// `pi/2` and infinity at the limit. Inputs that drive the f64
65    /// result outside `[D38::MIN, D38::MAX]` saturate per
66    /// [`Self::from_f64`].
67    ///
68    /// # Precision
69    ///
70    /// Lossy: involves f64 at some point; result may lose precision.
71    ///
72    /// # Examples
73    ///
74    /// ```ignore
75    /// # #[cfg(feature = "std")]
76    /// # {
77    /// use decimal_scaled::D38s12;
78    /// // tan(0) == 0 (bit-exact: f64::tan(0.0) == 0.0).
79    /// assert_eq!(D38s12::ZERO.tan(), D38s12::ZERO);
80    /// # }
81    /// ```
82    #[cfg(feature = "std")]
83    #[inline]
84    #[must_use]
85    pub fn tan_fast(self) -> Self {
86        Self::from_f64(self.to_f64().tan())
87    }
88
89    // ── Inverse trig (returns radians) ────────────────────────────────
90
91    /// Arcsine of `self`. Returns radians in `[-pi/2, pi/2]`.
92    ///
93    /// `f64::asin` returns NaN for inputs outside `[-1, 1]`, which
94    /// [`Self::from_f64`] maps to `D38::ZERO`.
95    ///
96    /// # Precision
97    ///
98    /// Lossy: involves f64 at some point; result may lose precision.
99    ///
100    /// # Examples
101    ///
102    /// ```ignore
103    /// # #[cfg(feature = "std")]
104    /// # {
105    /// use decimal_scaled::D38s12;
106    /// // asin(0) == 0.
107    /// assert_eq!(D38s12::ZERO.asin(), D38s12::ZERO);
108    /// # }
109    /// ```
110    #[cfg(feature = "std")]
111    #[inline]
112    #[must_use]
113    pub fn asin_fast(self) -> Self {
114        Self::from_f64(self.to_f64().asin())
115    }
116
117    /// Arccosine of `self`. Returns radians in `[0, pi]`.
118    ///
119    /// `f64::acos` returns NaN for inputs outside `[-1, 1]`, which
120    /// [`Self::from_f64`] maps to `D38::ZERO`.
121    ///
122    /// # Precision
123    ///
124    /// Lossy: involves f64 at some point; result may lose precision.
125    ///
126    /// # Examples
127    ///
128    /// ```ignore
129    /// # #[cfg(feature = "std")]
130    /// # {
131    /// use decimal_scaled::{D38s12, DecimalConstants};
132    /// // acos(1) == 0.
133    /// assert_eq!(D38s12::ONE.acos(), D38s12::ZERO);
134    /// # }
135    /// ```
136    #[cfg(feature = "std")]
137    #[inline]
138    #[must_use]
139    pub fn acos_fast(self) -> Self {
140        Self::from_f64(self.to_f64().acos())
141    }
142
143    /// Arctangent of `self`. Returns radians in `(-pi/2, pi/2)`.
144    ///
145    /// Defined for the entire real line.
146    ///
147    /// # Precision
148    ///
149    /// Lossy: involves f64 at some point; result may lose precision.
150    ///
151    /// # Examples
152    ///
153    /// ```ignore
154    /// # #[cfg(feature = "std")]
155    /// # {
156    /// use decimal_scaled::D38s12;
157    /// // atan(0) == 0.
158    /// assert_eq!(D38s12::ZERO.atan(), D38s12::ZERO);
159    /// # }
160    /// ```
161    #[cfg(feature = "std")]
162    #[inline]
163    #[must_use]
164    pub fn atan_fast(self) -> Self {
165        Self::from_f64(self.to_f64().atan())
166    }
167
168    /// Four-quadrant arctangent of `self` (`y`) over `other` (`x`).
169    /// Returns radians in `(-pi, pi]`.
170    ///
171    /// Signature matches `f64::atan2(self, other)`: the receiver is
172    /// `y` and the argument is `x`.
173    ///
174    /// # Precision
175    ///
176    /// Lossy: involves f64 at some point; result may lose precision.
177    ///
178    /// # Examples
179    ///
180    /// ```ignore
181    /// # #[cfg(feature = "std")]
182    /// # {
183    /// use decimal_scaled::{D38s12, DecimalConstants};
184    /// // atan2(1, 1) ~= pi/4 (45 degrees, first quadrant).
185    /// let one = D38s12::ONE;
186    /// let result = one.atan2(one); // approximately D38s12::quarter_pi()
187    /// # }
188    /// ```
189    #[cfg(feature = "std")]
190    #[inline]
191    #[must_use]
192    pub fn atan2_fast(self, other: Self) -> Self {
193        Self::from_f64(self.to_f64().atan2(other.to_f64()))
194    }
195
196    // ── Hyperbolic ────────────────────────────────────────────────────
197
198    /// Hyperbolic sine of `self`.
199    ///
200    /// Defined for the entire real line. Saturates at large magnitudes
201    /// per [`Self::from_f64`].
202    ///
203    /// # Precision
204    ///
205    /// Lossy: involves f64 at some point; result may lose precision.
206    ///
207    /// # Examples
208    ///
209    /// ```ignore
210    /// # #[cfg(feature = "std")]
211    /// # {
212    /// use decimal_scaled::D38s12;
213    /// // sinh(0) == 0.
214    /// assert_eq!(D38s12::ZERO.sinh(), D38s12::ZERO);
215    /// # }
216    /// ```
217    #[cfg(feature = "std")]
218    #[inline]
219    #[must_use]
220    pub fn sinh_fast(self) -> Self {
221        Self::from_f64(self.to_f64().sinh())
222    }
223
224    /// Hyperbolic cosine of `self`.
225    ///
226    /// Defined for the entire real line; result is always >= 1.
227    /// Saturates at large magnitudes per [`Self::from_f64`].
228    ///
229    /// # Precision
230    ///
231    /// Lossy: involves f64 at some point; result may lose precision.
232    ///
233    /// # Examples
234    ///
235    /// ```ignore
236    /// # #[cfg(feature = "std")]
237    /// # {
238    /// use decimal_scaled::D38s12;
239    /// // cosh(0) == 1.
240    /// assert_eq!(D38s12::ZERO.cosh(), D38s12::ONE);
241    /// # }
242    /// ```
243    #[cfg(feature = "std")]
244    #[inline]
245    #[must_use]
246    pub fn cosh_fast(self) -> Self {
247        Self::from_f64(self.to_f64().cosh())
248    }
249
250    /// Hyperbolic tangent of `self`.
251    ///
252    /// Defined for the entire real line; range is `(-1, 1)`.
253    ///
254    /// # Precision
255    ///
256    /// Lossy: involves f64 at some point; result may lose precision.
257    ///
258    /// # Examples
259    ///
260    /// ```ignore
261    /// # #[cfg(feature = "std")]
262    /// # {
263    /// use decimal_scaled::D38s12;
264    /// // tanh(0) == 0.
265    /// assert_eq!(D38s12::ZERO.tanh(), D38s12::ZERO);
266    /// # }
267    /// ```
268    #[cfg(feature = "std")]
269    #[inline]
270    #[must_use]
271    pub fn tanh_fast(self) -> Self {
272        Self::from_f64(self.to_f64().tanh())
273    }
274
275    /// Inverse hyperbolic sine of `self`.
276    ///
277    /// Defined for the entire real line.
278    ///
279    /// # Precision
280    ///
281    /// Lossy: involves f64 at some point; result may lose precision.
282    ///
283    /// # Examples
284    ///
285    /// ```ignore
286    /// # #[cfg(feature = "std")]
287    /// # {
288    /// use decimal_scaled::D38s12;
289    /// // asinh(0) == 0.
290    /// assert_eq!(D38s12::ZERO.asinh(), D38s12::ZERO);
291    /// # }
292    /// ```
293    #[cfg(feature = "std")]
294    #[inline]
295    #[must_use]
296    pub fn asinh_fast(self) -> Self {
297        Self::from_f64(self.to_f64().asinh())
298    }
299
300    /// Inverse hyperbolic cosine of `self`.
301    ///
302    /// `f64::acosh` returns NaN for inputs less than 1, which
303    /// [`Self::from_f64`] maps to `D38::ZERO`.
304    ///
305    /// # Precision
306    ///
307    /// Lossy: involves f64 at some point; result may lose precision.
308    ///
309    /// # Examples
310    ///
311    /// ```ignore
312    /// # #[cfg(feature = "std")]
313    /// # {
314    /// use decimal_scaled::D38s12;
315    /// // acosh(1) == 0.
316    /// assert_eq!(D38s12::ONE.acosh(), D38s12::ZERO);
317    /// # }
318    /// ```
319    #[cfg(feature = "std")]
320    #[inline]
321    #[must_use]
322    pub fn acosh_fast(self) -> Self {
323        Self::from_f64(self.to_f64().acosh())
324    }
325
326    /// Inverse hyperbolic tangent of `self`.
327    ///
328    /// `f64::atanh` returns NaN for inputs outside `(-1, 1)`, which
329    /// [`Self::from_f64`] maps to `D38::ZERO`.
330    ///
331    /// # Precision
332    ///
333    /// Lossy: involves f64 at some point; result may lose precision.
334    ///
335    /// # Examples
336    ///
337    /// ```ignore
338    /// # #[cfg(feature = "std")]
339    /// # {
340    /// use decimal_scaled::D38s12;
341    /// // atanh(0) == 0.
342    /// assert_eq!(D38s12::ZERO.atanh(), D38s12::ZERO);
343    /// # }
344    /// ```
345    #[cfg(feature = "std")]
346    #[inline]
347    #[must_use]
348    pub fn atanh_fast(self) -> Self {
349        Self::from_f64(self.to_f64().atanh())
350    }
351
352    // ── Angle conversions ─────────────────────────────────────────────
353
354    /// Convert radians to degrees: `self * (180 / pi)`.
355    ///
356    /// Routed through `f64::to_degrees` so results match the de facto
357    /// reference produced by the rest of the Rust ecosystem. Multiplying
358    /// by a precomputed `D38` factor derived from `D38::pi()` would
359    /// diverge from f64 by a 1-LSB rescale rounding without any
360    /// practical determinism gain, since the f64 bridge is already the
361    /// precision floor.
362    ///
363    /// # Precision
364    ///
365    /// Lossy: involves f64 at some point; result may lose precision.
366    ///
367    /// # Examples
368    ///
369    /// ```ignore
370    /// # #[cfg(feature = "std")]
371    /// # {
372    /// use decimal_scaled::D38s12;
373    /// // to_degrees(0) == 0.
374    /// assert_eq!(D38s12::ZERO.to_degrees(), D38s12::ZERO);
375    /// # }
376    /// ```
377    #[cfg(feature = "std")]
378    #[inline]
379    #[must_use]
380    pub fn to_degrees_fast(self) -> Self {
381        Self::from_f64(self.to_f64().to_degrees())
382    }
383
384    /// Convert degrees to radians: `self * (pi / 180)`.
385    ///
386    /// Routed through `f64::to_radians`. See [`Self::to_degrees`] for
387    /// the rationale.
388    ///
389    /// # Precision
390    ///
391    /// Lossy: involves f64 at some point; result may lose precision.
392    ///
393    /// # Examples
394    ///
395    /// ```ignore
396    /// # #[cfg(feature = "std")]
397    /// # {
398    /// use decimal_scaled::D38s12;
399    /// // to_radians(0) == 0.
400    /// assert_eq!(D38s12::ZERO.to_radians(), D38s12::ZERO);
401    /// # }
402    /// ```
403    #[cfg(feature = "std")]
404    #[inline]
405    #[must_use]
406    pub fn to_radians_fast(self) -> Self {
407        Self::from_f64(self.to_f64().to_radians())
408    }
409}
410
411#[cfg(all(feature = "std", any(not(feature = "strict"), feature = "fast")))]
412impl<const SCALE: u32> crate::D<crate::int::types::Int<2>, SCALE> {
413    /// Plain dispatcher: forwards to [`Self::sin_fast`] in this feature mode.
414    #[inline]
415    #[must_use]
416    pub fn sin(self) -> Self {
417        self.sin_fast()
418    }
419    /// Plain dispatcher: forwards to [`Self::cos_fast`] in this feature mode.
420    #[inline]
421    #[must_use]
422    pub fn cos(self) -> Self {
423        self.cos_fast()
424    }
425    /// Plain dispatcher: forwards to [`Self::tan_fast`] in this feature mode.
426    #[inline]
427    #[must_use]
428    pub fn tan(self) -> Self {
429        self.tan_fast()
430    }
431    /// Plain dispatcher: forwards to [`Self::asin_fast`] in this feature mode.
432    #[inline]
433    #[must_use]
434    pub fn asin(self) -> Self {
435        self.asin_fast()
436    }
437    /// Plain dispatcher: forwards to [`Self::acos_fast`] in this feature mode.
438    #[inline]
439    #[must_use]
440    pub fn acos(self) -> Self {
441        self.acos_fast()
442    }
443    /// Plain dispatcher: forwards to [`Self::atan_fast`] in this feature mode.
444    #[inline]
445    #[must_use]
446    pub fn atan(self) -> Self {
447        self.atan_fast()
448    }
449    /// Plain dispatcher: forwards to [`Self::atan2_fast`] in this feature mode.
450    #[inline]
451    #[must_use]
452    pub fn atan2(self, other: Self) -> Self {
453        self.atan2_fast(other)
454    }
455    /// Plain dispatcher: forwards to [`Self::sinh_fast`] in this feature mode.
456    #[inline]
457    #[must_use]
458    pub fn sinh(self) -> Self {
459        self.sinh_fast()
460    }
461    /// Plain dispatcher: forwards to [`Self::cosh_fast`] in this feature mode.
462    #[inline]
463    #[must_use]
464    pub fn cosh(self) -> Self {
465        self.cosh_fast()
466    }
467    /// Plain dispatcher: forwards to [`Self::tanh_fast`] in this feature mode.
468    #[inline]
469    #[must_use]
470    pub fn tanh(self) -> Self {
471        self.tanh_fast()
472    }
473    /// Plain dispatcher: forwards to [`Self::asinh_fast`] in this feature mode.
474    #[inline]
475    #[must_use]
476    pub fn asinh(self) -> Self {
477        self.asinh_fast()
478    }
479    /// Plain dispatcher: forwards to [`Self::acosh_fast`] in this feature mode.
480    #[inline]
481    #[must_use]
482    pub fn acosh(self) -> Self {
483        self.acosh_fast()
484    }
485    /// Plain dispatcher: forwards to [`Self::atanh_fast`] in this feature mode.
486    #[inline]
487    #[must_use]
488    pub fn atanh(self) -> Self {
489        self.atanh_fast()
490    }
491    /// Plain dispatcher: forwards to [`Self::to_degrees_fast`] in this feature mode.
492    #[inline]
493    #[must_use]
494    pub fn to_degrees(self) -> Self {
495        self.to_degrees_fast()
496    }
497    /// Plain dispatcher: forwards to [`Self::to_radians_fast`] in this feature mode.
498    #[inline]
499    #[must_use]
500    pub fn to_radians(self) -> Self {
501        self.to_radians_fast()
502    }
503}