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}