spinoso_math/
math.rs

1#[cfg(feature = "full")]
2use core::num::FpCategory;
3
4use crate::{DomainError, NotImplementedError};
5
6/// Computes the arccosine of the given value. Returns results in the range
7/// `(0..=PI)`.
8///
9/// Domain: [-1, 1]
10///
11/// Codomain: [0, PI]
12///
13/// # Examples
14///
15/// ```
16/// # use spinoso_math::PI;
17/// use spinoso_math as math;
18/// assert_eq!(math::acos(0.0), Ok(PI / 2.0));
19/// assert!(math::acos(100.0).is_err());
20///
21/// assert!(matches!(math::acos(f64::NAN), Ok(result) if result.is_nan()));
22/// ```
23///
24/// # Errors
25///
26/// If the result of computing the arccosine is [`NAN`], a domain error is
27/// returned.
28///
29/// [`NAN`]: f64::NAN
30#[inline]
31pub fn acos(value: f64) -> Result<f64, DomainError> {
32    if value.is_nan() {
33        return Ok(f64::NAN);
34    }
35    let result = value.acos();
36    if result.is_nan() {
37        Err(DomainError::with_message(
38            r#"Numerical argument is out of domain - "acos""#,
39        ))
40    } else {
41        Ok(result)
42    }
43}
44
45/// Computes the inverse hyperbolic cosine of the given value.
46///
47/// Domain: [1, INFINITY)
48///
49/// Codomain: [0, INFINITY)
50///
51/// # Examples
52///
53/// ```
54/// use spinoso_math as math;
55/// assert_eq!(math::acosh(1.0), Ok(0.0));
56/// assert!(math::acosh(0.0).is_err());
57///
58/// assert!(matches!(math::acosh(f64::NAN), Ok(result) if result.is_nan()));
59/// ```
60///
61/// # Errors
62///
63/// If the result of computing the inverse hyperbolic cosine is [`NAN`], a
64/// domain error is returned.
65///
66/// [`NAN`]: f64::NAN
67#[inline]
68pub fn acosh(value: f64) -> Result<f64, DomainError> {
69    if value.is_nan() {
70        return Ok(f64::NAN);
71    }
72    let result = value.acosh();
73    if result.is_nan() {
74        Err(DomainError::with_message(
75            r#"Numerical argument is out of domain - "acosh""#,
76        ))
77    } else {
78        Ok(result)
79    }
80}
81
82/// Computes the arcsine of the given value. Returns results in the range
83/// `(-PI/2..=PI/2)`.
84///
85/// Domain: [-1, -1]
86///
87/// Codomain: [-PI/2, PI/2]
88///
89/// # Examples
90///
91/// ```
92/// # use spinoso_math::PI;
93/// use spinoso_math as math;
94/// assert_eq!(math::asin(1.0), Ok(PI / 2.0));
95/// assert!(math::asin(100.0).is_err());
96///
97/// assert!(matches!(math::asin(f64::NAN), Ok(result) if result.is_nan()));
98/// ```
99///
100/// # Errors
101///
102/// If the result of computing the arcsine is [`NAN`], a domain error is
103/// returned.
104///
105/// [`NAN`]: f64::NAN
106#[inline]
107pub fn asin(value: f64) -> Result<f64, DomainError> {
108    if value.is_nan() {
109        return Ok(f64::NAN);
110    }
111    let result = value.asin();
112    if result.is_nan() {
113        Err(DomainError::with_message(
114            r#"Numerical argument is out of domain - "asin""#,
115        ))
116    } else {
117        Ok(result)
118    }
119}
120
121/// Computes the inverse hyperbolic sine of the given value.
122///
123/// Domain: (-INFINITY, INFINITY)
124///
125/// Codomain: (-INFINITY, INFINITY)
126///
127/// # Examples
128///
129/// ```
130/// use spinoso_math as math;
131/// assert!((math::asinh(1.0) - 0.881373587019543).abs() < f64::EPSILON);
132/// ```
133#[inline]
134#[must_use]
135pub fn asinh(value: f64) -> f64 {
136    value.asinh()
137}
138
139/// Computes the arctangent of the given value. Returns results in the range
140/// `(-PI/2..=PI/2)`.
141///
142/// Domain: (-INFINITY, INFINITY)
143///
144/// Codomain: (-PI/2, PI/2)
145///
146/// # Examples
147///
148/// ```
149/// use spinoso_math as math;
150/// assert_eq!(math::atan(0.0), 0.0);
151/// ```
152#[inline]
153#[must_use]
154pub fn atan(value: f64) -> f64 {
155    value.atan()
156}
157
158/// Computes the four quadrant arctangent of `value` (`y`) and `other` (`x`) in
159/// radians.
160///
161/// Return value is a angle in radians between the positive x-axis of Cartesian
162/// plane and the point given by the coordinates (`x`, `y`) on it.
163///
164/// Domain: (-INFINITY, INFINITY)
165///
166/// Codomain: [-PI, PI]
167///
168/// # Examples
169///
170/// ```
171/// use spinoso_math as math;
172/// assert!((math::atan2(-0.0, -1.0) - (-3.141592653589793)).abs() < f64::EPSILON);
173/// assert!((math::atan2(-1.0, -1.0) - (-2.356194490192345)).abs() < f64::EPSILON);
174/// assert!((math::atan2(-1.0, 0.0) - (-1.5707963267948966)).abs() < f64::EPSILON);
175/// assert!((math::atan2(-1.0, 1.0) - (-0.7853981633974483)).abs() < f64::EPSILON);
176/// assert!(math::atan2(-0.0, 1.0) == -0.0);
177/// assert!(math::atan2(0.0, 1.0) == 0.0);
178/// assert!((math::atan2(1.0, 1.0) - 0.7853981633974483).abs() < f64::EPSILON);
179/// assert!((math::atan2(1.0, 0.0) - 1.5707963267948966).abs() < f64::EPSILON);
180/// assert!((math::atan2(1.0, -1.0) - 2.356194490192345).abs() < f64::EPSILON);
181/// assert!((math::atan2(0.0, -1.0) - 3.141592653589793).abs() < f64::EPSILON);
182/// assert!((math::atan2(f64::INFINITY, f64::INFINITY) - 0.7853981633974483).abs() < f64::EPSILON);
183/// assert!(
184///     (math::atan2(f64::INFINITY, f64::NEG_INFINITY) - 2.356194490192345).abs() < f64::EPSILON
185/// );
186/// assert!(
187///     (math::atan2(f64::NEG_INFINITY, f64::INFINITY) - (-0.7853981633974483)).abs()
188///         < f64::EPSILON
189/// );
190/// assert!(
191///     (math::atan2(f64::NEG_INFINITY, f64::NEG_INFINITY) - (-2.356194490192345)).abs()
192///         < f64::EPSILON
193/// );
194/// ```
195#[inline]
196#[must_use]
197pub fn atan2(value: f64, other: f64) -> f64 {
198    value.atan2(other)
199}
200
201/// Computes the inverse hyperbolic tangent of the given value.
202///
203/// Domain: (-1, 1)
204///
205/// Codomain: (-INFINITY, INFINITY)
206///
207/// # Examples
208///
209/// ```
210/// use spinoso_math as math;
211/// assert_eq!(math::atanh(1.0), Ok(f64::INFINITY));
212/// assert!(math::atanh(100.0).is_err());
213///
214/// assert!(matches!(math::atanh(f64::NAN), Ok(result) if result.is_nan()));
215/// ```
216///
217/// # Errors
218///
219/// If the result of computing the inverse hyperbolic tangent is [`NAN`]
220/// a domain error is returned.
221///
222/// [`NAN`]: f64::NAN
223#[inline]
224pub fn atanh(value: f64) -> Result<f64, DomainError> {
225    if value.is_nan() {
226        return Ok(f64::NAN);
227    }
228    let result = value.atanh();
229    if result.is_nan() {
230        Err(DomainError::with_message(
231            r#"Numerical argument is out of domain - "atanh""#,
232        ))
233    } else {
234        Ok(result)
235    }
236}
237
238/// Returns the cube root of the given value.
239///
240/// Domain: (-INFINITY, INFINITY)
241///
242/// Codomain: (-INFINITY, INFINITY)
243///
244/// # Examples
245///
246/// ```
247/// use spinoso_math as math;
248/// assert!((math::cbrt(-9.0) - (-2.080083823051904)).abs() < f64::EPSILON);
249/// assert!((math::cbrt(9.0) - 2.080083823051904).abs() < f64::EPSILON);
250/// ```
251#[inline]
252#[must_use]
253pub fn cbrt(value: f64) -> f64 {
254    value.cbrt()
255}
256
257/// Computes the cosine of the given value (expressed in radians). Returns
258/// values in the range `-1.0..=1.0`.
259///
260/// Domain: (-INFINITY, INFINITY)
261///
262/// Codomain: [-1, 1]
263///
264/// # Examples
265///
266/// ```
267/// # use spinoso_math::PI;
268/// use spinoso_math as math;
269/// assert_eq!(math::cos(PI), -1.0);
270/// ```
271#[inline]
272#[must_use]
273pub fn cos(value: f64) -> f64 {
274    value.cos()
275}
276
277/// Computes the hyperbolic cosine of the given value (expressed in radians).
278///
279/// Domain: (-INFINITY, INFINITY)
280///
281/// Codomain: [1, INFINITY)
282///
283/// # Examples
284///
285/// ```
286/// use spinoso_math as math;
287/// assert_eq!(math::cosh(0.0), 1.0);
288/// ```
289#[inline]
290#[must_use]
291pub fn cosh(value: f64) -> f64 {
292    value.cosh()
293}
294
295/// Calculates the error function of the given value.
296///
297/// Domain: (-INFINITY, INFINITY)
298///
299/// Codomain: (-1, 1)
300///
301/// # Errors
302///
303/// Because `spinoso-math` was built without the `full` feature, this function
304/// will always return a not implemented error.
305#[inline]
306#[cfg(not(feature = "full"))]
307pub fn erf(value: f64) -> Result<f64, NotImplementedError> {
308    let _ = value;
309    Err(NotImplementedError::with_message(
310        "Artichoke was not built with Math::erf support",
311    ))
312}
313
314/// Calculates the error function of the given value.
315///
316/// Domain: (-INFINITY, INFINITY)
317///
318/// Codomain: (-1, 1)
319///
320/// # Examples
321///
322/// ```
323/// use spinoso_math as math;
324/// assert_eq!(math::erf(0.0), Ok(0.0));
325/// ```
326///
327/// # Errors
328///
329/// Because `spinoso-math` was built with the `full` feature, this function will
330/// always succeed and return the error function of the given value.
331#[inline]
332#[cfg(feature = "full")]
333pub fn erf(value: f64) -> Result<f64, NotImplementedError> {
334    let result = libm::erf(value);
335    Ok(result)
336}
337
338/// Calculates the complementary error function of the given value.
339///
340/// Domain: (-INFINITY, INFINITY)
341///
342/// Codomain: (0, 2)
343///
344/// # Errors
345///
346/// Because `spinoso-math` was built without the `full` feature, this function
347/// will always return a not implemented error.
348#[inline]
349#[cfg(not(feature = "full"))]
350pub fn erfc(value: f64) -> Result<f64, NotImplementedError> {
351    let _ = value;
352    Err(NotImplementedError::with_message(
353        "Artichoke was not built with Math::erfc support",
354    ))
355}
356
357/// Calculates the complementary error function of the given value.
358///
359/// Domain: (-INFINITY, INFINITY)
360///
361/// Codomain: (0, 2)
362///
363/// # Examples
364///
365/// ```
366/// use spinoso_math as math;
367/// assert_eq!(math::erfc(0.0), Ok(1.0));
368/// ```
369///
370/// # Errors
371///
372/// Because `spinoso-math` was built with the `full` feature, this function will
373/// always succeed and return the complementary error function of the given
374/// value.
375#[inline]
376#[cfg(feature = "full")]
377pub fn erfc(value: f64) -> Result<f64, NotImplementedError> {
378    let result = libm::erfc(value);
379    Ok(result)
380}
381
382/// Returns `e**x`.
383///
384/// Domain: (-INFINITY, INFINITY)
385///
386/// Codomain: (0, INFINITY)
387///
388/// # Examples
389///
390/// ```
391/// # use spinoso_math::E;
392/// use spinoso_math as math;
393/// assert_eq!(math::exp(0.0), 1.0);
394/// # #[cfg(not(artichoke_sanitizers))]
395/// assert_eq!(math::exp(1.0), E);
396/// # #[cfg(not(artichoke_sanitizers))]
397/// assert!((math::exp(1.5) - 4.4816890703380645).abs() < f64::EPSILON);
398/// ```
399#[inline]
400#[must_use]
401pub fn exp(value: f64) -> f64 {
402    value.exp()
403}
404
405/// Returns a tuple array containing the normalized fraction (a Float) and
406/// exponent (an Integer) of the given value.
407///
408/// # Errors
409///
410/// Because `spinoso-math` was built without the `full` feature, this function
411/// will always return a not implemented error.
412#[inline]
413#[cfg(not(feature = "full"))]
414pub const fn frexp(value: f64) -> Result<(f64, i32), NotImplementedError> {
415    let _ = value;
416    Err(NotImplementedError::with_message(
417        "Artichoke was not built with Math::frexp support",
418    ))
419}
420
421/// Returns a tuple array containing the normalized fraction (a Float) and
422/// exponent (an Integer) of the given value.
423///
424/// # Examples
425///
426/// ```
427/// use spinoso_math as math;
428/// # fn example() -> Result<(), math::NotImplementedError> {
429/// let (fraction, exponent) = math::frexp(1234.0)?;
430/// let float = math::ldexp(fraction, exponent)?;
431/// assert_eq!(float, 1234.0);
432/// # Ok(())
433/// # }
434/// # example().unwrap();
435/// ```
436///
437/// # Errors
438///
439/// Because `spinoso-math` was built with the `full` feature, this function will
440/// always succeed and return the normalized fraction and exponent of the given
441/// value.
442#[inline]
443#[cfg(feature = "full")]
444pub fn frexp(value: f64) -> Result<(f64, i32), NotImplementedError> {
445    let result = libm::frexp(value);
446    Ok(result)
447}
448
449/// Calculates the gamma function of the given value.
450///
451/// Note that `gamma(n)` is same as `fact(n-1)` for integer `n > 0`. However
452/// `gamma(n)` returns float and can be an approximation.
453///
454/// # Errors
455///
456/// Because `spinoso-math` was built without the `full` feature, this function
457/// will always return a not implemented error.
458#[cfg(not(feature = "full"))]
459pub const fn gamma(value: f64) -> Result<f64, NotImplementedError> {
460    let _ = value;
461    Err(NotImplementedError::with_message(
462        "Artichoke was not built with Math::gamma support",
463    ))
464}
465
466/// Calculates the gamma function of the given value.
467///
468/// Note that `gamma(n)` is same as `fact(n-1)` for integer `n > 0`. However
469/// `gamma(n)` returns float and can be an approximation.
470///
471/// # Examples
472///
473/// ```
474/// use spinoso_math as math;
475/// assert_eq!(math::gamma(1.0), Ok(1.0));
476/// assert_eq!(math::gamma(2.0), Ok(1.0));
477/// assert_eq!(math::gamma(3.0), Ok(2.0));
478/// assert_eq!(math::gamma(4.0), Ok(6.0));
479/// assert_eq!(math::gamma(5.0), Ok(24.0));
480/// assert_eq!(math::gamma(20.0), Ok(1.21645100408832e+17));
481///
482/// assert!(math::gamma(-15.0).is_err());
483/// assert!(matches!(math::gamma(-15.1), Ok(result) if (result - 5.9086389724319095e-12).abs() < f64::EPSILON));
484///
485/// assert!(math::gamma(f64::NEG_INFINITY).is_err());
486/// assert_eq!(math::gamma(f64::INFINITY), Ok(f64::INFINITY));
487/// ```
488///
489/// # Errors
490///
491/// If the given value is negative, a domain error is returned.
492#[inline]
493#[cfg(feature = "full")]
494pub fn gamma(value: f64) -> Result<f64, DomainError> {
495    // `gamma(n)` is the same as `n!` for integer `n > 0`. `gamma` returns float
496    // and might be an approximation so include a lookup table for as many `n`
497    // as can fit in the float mantissa.
498    const FACTORIAL_TABLE: [f64; 23] = [
499        1.0_f64,                         // fact(0)
500        1.0,                             // fact(1)
501        2.0,                             // fact(2)
502        6.0,                             // fact(3)
503        24.0,                            // fact(4)
504        120.0,                           // fact(5)
505        720.0,                           // fact(6)
506        5_040.0,                         // fact(7)
507        40_320.0,                        // fact(8)
508        362_880.0,                       // fact(9)
509        3_628_800.0,                     // fact(10)
510        39_916_800.0,                    // fact(11)
511        479_001_600.0,                   // fact(12)
512        6_227_020_800.0,                 // fact(13)
513        87_178_291_200.0,                // fact(14)
514        1_307_674_368_000.0,             // fact(15)
515        20_922_789_888_000.0,            // fact(16)
516        355_687_428_096_000.0,           // fact(17)
517        6_402_373_705_728_000.0,         // fact(18)
518        121_645_100_408_832_000.0,       // fact(19)
519        2_432_902_008_176_640_000.0,     // fact(20)
520        51_090_942_171_709_440_000.0,    // fact(21)
521        1_124_000_727_777_607_680_000.0, // fact(22)
522    ];
523    match value {
524        value if value.is_infinite() && value.is_sign_negative() => Err(DomainError::with_message(
525            r#"Numerical argument is out of domain - "gamma""#,
526        )),
527        value if value.is_infinite() => Ok(f64::INFINITY),
528        value if matches!(value.classify(), FpCategory::Zero) && value.is_sign_negative() => Ok(f64::NEG_INFINITY),
529        value if matches!(value.classify(), FpCategory::Zero) => Ok(f64::INFINITY),
530        value if (value - value.floor()).abs() < f64::EPSILON && value.is_sign_negative() => Err(
531            DomainError::with_message(r#"Numerical argument is out of domain - "gamma""#),
532        ),
533        value if (value - value.floor()).abs() < f64::EPSILON => {
534            #[expect(
535                clippy::cast_possible_truncation,
536                reason = "saturating to i64::MAX is ok since we're attempting to index into a [f64; 23]"
537            )]
538            let idx = (value as i64).checked_sub(1).map(usize::try_from);
539            if let Some(Ok(idx)) = idx {
540                if let Some(&result) = FACTORIAL_TABLE.get(idx) {
541                    return Ok(result);
542                }
543            }
544            let result = libm::tgamma(value);
545            Ok(result)
546        }
547        value => {
548            let result = libm::tgamma(value);
549            Ok(result)
550        }
551    }
552}
553
554/// Returns `sqrt(x**2 + y**2)`, the hypotenuse of a right-angled triangle with
555/// sides x and y.
556///
557/// # Examples
558///
559/// ```
560/// use spinoso_math as math;
561/// assert_eq!(math::hypot(3.0, 4.0), 5.0);
562/// ```
563#[inline]
564#[must_use]
565pub fn hypot(x: f64, y: f64) -> f64 {
566    x.hypot(y)
567}
568
569/// Returns the value of `fraction * (2**exponent)`.
570///
571/// # Errors
572///
573/// Because `spinoso-math` was built without the `full` feature, this function
574/// will always return a not implemented error.
575#[cfg(not(feature = "full"))]
576pub fn ldexp(fraction: f64, exponent: i32) -> Result<f64, NotImplementedError> {
577    let _ = fraction;
578    let _ = exponent;
579    Err(NotImplementedError::with_message(
580        "Artichoke was not built with Math::ldexp support",
581    ))
582}
583
584/// Returns the value of `fraction * (2**exponent)`.
585///
586/// # Examples
587///
588/// ```
589/// use spinoso_math as math;
590/// # fn example() -> Result<(), math::NotImplementedError> {
591/// let (fraction, exponent) = math::frexp(1234.0)?;
592/// let float = math::ldexp(fraction, exponent)?;
593/// assert_eq!(float, 1234.0);
594/// # Ok(())
595/// # }
596/// # example().unwrap();
597/// ```
598///
599/// # Errors
600///
601/// Because `spinoso-math` was built with the `full` feature, this function will
602/// always succeed and return the float determined by the given fraction and
603/// exponent.
604#[inline]
605#[cfg(feature = "full")]
606pub fn ldexp(fraction: f64, exponent: i32) -> Result<f64, NotImplementedError> {
607    let result = libm::ldexp(fraction, exponent);
608    Ok(result)
609}
610
611/// Calculates the logarithmic gamma of value and the sign of gamma of value.
612///
613/// `lgamma` is same as:
614///
615/// ```ruby
616/// [Math.log(Math.gamma(x).abs), Math.gamma(x) < 0 ? -1 : 1]
617/// ```
618///
619/// but avoids overflow of `gamma` for large values.
620///
621/// # Errors
622///
623/// Because `spinoso-math` was built without the `full` feature, this function
624/// will always return a not implemented error.
625#[inline]
626#[cfg(not(feature = "full"))]
627pub fn lgamma(value: f64) -> Result<(f64, i32), NotImplementedError> {
628    let _ = value;
629    Err(NotImplementedError::with_message(
630        "Artichoke was not built with Math::lgamma support",
631    ))
632}
633
634/// Calculates the logarithmic gamma of value and the sign of gamma of value.
635///
636/// `lgamma` is same as:
637///
638/// ```ruby
639/// [Math.log(Math.gamma(x).abs), Math.gamma(x) < 0 ? -1 : 1]
640/// ```
641///
642/// but avoids overflow of `gamma` for large values.
643///
644/// # Examples
645///
646/// ```
647/// use spinoso_math as math;
648/// assert_eq!(math::lgamma(0.0), Ok((f64::INFINITY, 1)));
649///
650/// assert!(math::lgamma(f64::NEG_INFINITY).is_err());
651/// ```
652///
653/// # Errors
654///
655/// If the given value is [negative infinity], an error is returned.
656///
657/// [negative infinity]: f64::NEG_INFINITY
658#[inline]
659#[cfg(feature = "full")]
660pub fn lgamma(value: f64) -> Result<(f64, i32), DomainError> {
661    if value.is_infinite() && value.is_sign_negative() {
662        Err(DomainError::with_message(
663            r#"Numerical argument is out of domain - "lgamma""#,
664        ))
665    } else {
666        let (result, sign) = libm::lgamma_r(value);
667        Ok((result, sign))
668    }
669}
670
671/// Returns the logarithm of the number with respect to an arbitrary base.
672///
673/// Domain: (0, INFINITY)
674///
675/// Codomain: (-INFINITY, INFINITY)
676///
677/// # Examples
678///
679/// ```
680/// # use spinoso_math::E;
681/// use spinoso_math as math;
682/// assert_eq!(math::log(1.0, None), Ok(0.0));
683/// assert_eq!(math::log(E, None), Ok(1.0));
684/// assert_eq!(math::log(64.0, Some(4.0)), Ok(3.0));
685///
686/// assert_eq!(math::log(0.0, None), Ok(f64::NEG_INFINITY));
687/// assert!(math::log(-0.1, None).is_err());
688///
689/// assert!(matches!(math::log(f64::NAN, None), Ok(result) if result.is_nan()));
690/// ```
691///
692/// # Errors
693///
694/// If the given arbitrary base is [`NAN`], a domain error is returned.
695///
696/// If the result of computing the logarithm is [`NAN`], a domain error is
697/// returned.
698///
699/// [`NAN`]: f64::NAN
700#[inline]
701pub fn log(value: f64, base: Option<f64>) -> Result<f64, DomainError> {
702    if value.is_nan() {
703        return Ok(f64::NAN);
704    }
705    let result = match base {
706        Some(base) if base.is_nan() => return Ok(f64::NAN),
707        Some(base) => value.log(base),
708        None => value.ln(),
709    };
710    if result.is_nan() {
711        Err(DomainError::with_message(
712            r#"Numerical argument is out of domain - "log""#,
713        ))
714    } else {
715        Ok(result)
716    }
717}
718
719/// Returns the base 10 logarithm of the number.
720///
721/// Domain: (0, INFINITY)
722///
723/// Codomain: (-INFINITY, INFINITY)
724///
725/// # Examples
726///
727/// ```
728/// use spinoso_math as math;
729/// assert_eq!(math::log10(1.0), Ok(0.0));
730/// assert_eq!(math::log10(10.0), Ok(1.0));
731/// assert_eq!(math::log10(1e100), Ok(100.0));
732///
733/// assert_eq!(math::log10(0.0), Ok(f64::NEG_INFINITY));
734/// assert!(math::log10(-0.1).is_err());
735///
736/// assert!(matches!(math::log10(f64::NAN), Ok(result) if result.is_nan()));
737/// ```
738///
739/// # Errors
740///
741/// If the result of computing the logarithm is [`NAN`], a domain error is
742/// returned.
743///
744/// [`NAN`]: f64::NAN
745#[inline]
746pub fn log10(value: f64) -> Result<f64, DomainError> {
747    if value.is_nan() {
748        return Ok(f64::NAN);
749    }
750    let result = value.log10();
751    if result.is_nan() {
752        Err(DomainError::with_message(
753            r#"Numerical argument is out of domain - "log10""#,
754        ))
755    } else {
756        Ok(result)
757    }
758}
759
760/// Returns the base 2 logarithm of the number.
761///
762/// Domain: (0, INFINITY)
763///
764/// Codomain: (-INFINITY, INFINITY)
765///
766/// # Examples
767///
768/// ```
769/// use spinoso_math as math;
770/// assert_eq!(math::log2(1.0), Ok(0.0));
771/// assert_eq!(math::log2(2.0), Ok(1.0));
772/// assert_eq!(math::log2(32768.0), Ok(15.0));
773/// assert_eq!(math::log2(65536.0), Ok(16.0));
774///
775/// assert_eq!(math::log2(0.0), Ok(f64::NEG_INFINITY));
776/// assert!(math::log2(-0.1).is_err());
777///
778/// assert!(matches!(math::log2(f64::NAN), Ok(result) if result.is_nan()));
779/// ```
780///
781/// # Errors
782///
783/// If the result of computing the logarithm is [`NAN`], a domain error is
784/// returned.
785///
786/// [`NAN`]: f64::NAN
787#[inline]
788pub fn log2(value: f64) -> Result<f64, DomainError> {
789    if value.is_nan() {
790        return Ok(f64::NAN);
791    }
792    let result = value.log2();
793    if result.is_nan() {
794        Err(DomainError::with_message(
795            r#"Numerical argument is out of domain - "log2""#,
796        ))
797    } else {
798        Ok(result)
799    }
800}
801
802/// Computes the sine of the given value (expressed in radians). Returns a Float
803/// in the range `-1.0..=1.0`.
804///
805/// Domain: (-INFINITY, INFINITY)
806///
807/// Codomain: [-1, 1]
808///
809/// # Examples
810///
811/// ```
812/// use spinoso_math as math;
813/// assert_eq!(math::sin(math::PI / 2.0), 1.0);
814/// ```
815#[inline]
816#[must_use]
817pub fn sin(value: f64) -> f64 {
818    value.sin()
819}
820
821/// Computes the hyperbolic sine of the given value (expressed in radians).
822///
823/// Domain: (-INFINITY, INFINITY)
824///
825/// Codomain: (-INFINITY, INFINITY)
826///
827/// # Examples
828///
829/// ```
830/// use spinoso_math as math;
831/// assert_eq!(math::sinh(0.0), 0.0);
832/// ```
833#[inline]
834#[must_use]
835pub fn sinh(value: f64) -> f64 {
836    value.sinh()
837}
838
839/// Returns the non-negative square root of the given value.
840///
841/// Domain: [0, INFINITY)
842///
843/// Codomain: [0, INFINITY)
844///
845/// # Examples
846///
847/// ```
848/// # use spinoso_math::DomainError;
849/// use spinoso_math as math;
850/// assert_eq!(math::sqrt(0.0), Ok(0.0));
851/// assert_eq!(math::sqrt(1.0), Ok(1.0));
852/// assert_eq!(math::sqrt(9.0), Ok(3.0));
853///
854/// assert!(math::sqrt(-9.0).is_err());
855///
856/// assert!(matches!(math::sqrt(f64::NAN), Ok(result) if result.is_nan()));
857/// ```
858///
859/// # Errors
860///
861/// If the result of computing the square root is [`NAN`], a domain error is
862/// returned.
863///
864/// [`NAN`]: f64::NAN
865#[inline]
866pub fn sqrt(value: f64) -> Result<f64, DomainError> {
867    if value.is_nan() {
868        return Ok(f64::NAN);
869    }
870    let result = value.sqrt();
871    if result.is_nan() {
872        Err(DomainError::with_message(
873            r#"Numerical argument is out of domain - "sqrt""#,
874        ))
875    } else {
876        Ok(result)
877    }
878}
879
880/// Computes the tangent of the given value (expressed in radians).
881///
882/// Domain: (-INFINITY, INFINITY)
883///
884/// Codomain: (-INFINITY, INFINITY)
885///
886/// # Examples
887///
888/// ```
889/// use spinoso_math as math;
890/// assert_eq!(math::tan(0.0), 0.0);
891/// ```
892#[inline]
893#[must_use]
894pub fn tan(value: f64) -> f64 {
895    value.tan()
896}
897
898/// Computes the hyperbolic tangent of the given value (expressed in radians).
899///
900/// Domain: (-INFINITY, INFINITY)
901///
902/// Codomain: (-1, 1)
903///
904/// # Examples
905///
906/// ```
907/// use spinoso_math as math;
908/// assert_eq!(math::tanh(0.0), 0.0);
909/// ```
910#[inline]
911#[must_use]
912pub fn tanh(value: f64) -> f64 {
913    value.tanh()
914}