Skip to main content

mathverse_core/
traits.rs

1//! Numeric abstraction traits.
2//!
3//! Every `MathVerse` crate is generic over these traits. The hierarchy is:
4//!
5//! ```text
6//!                         ┌─────────┐
7//!                         │   Num   │   Integer + Float
8//!                         └────┬────┘
9//!                              │
10//!                     ┌────────┴────────┐
11//!                     │                 │
12//!                ┌────┴────┐     ┌─────┴─────┐
13//!                │ Signed  │     │ Unsigned* │  (*marker, not enforced)
14//!                └────┬────┘     └───────────┘
15//!                     │
16//!            ┌────────┴────────┐
17//!            │                 │
18//!       ┌────┴────┐     ┌──────┴──────┐
19//!       │  Field  │     │   (marker)  │
20//!       └────┬────┘     └─────────────┘
21//!            │
22//!       ┌────┴──────────────────────────────────┐
23//!       │                Real                   │  core: conversion, powers, rounding
24//!       └────┬────────┬─────────┬───────┬───────┘
25//!            │        │         │       │       │
26//!   Transcendental  Trig  Hyperbolic  FloatOps  FloatClass
27//!            │        │         │       │       │
28//!       └────┴────────┴─────────┴───────┴───────┘
29//!                          │
30//!                    ┌─────┴─────┐
31//!                    │ RealFull  │  backward-compat supertrait
32//!                    └───────────┘
33//! ```
34
35use core::fmt::Debug;
36use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
37
38/// Foundation: anything that can add, subtract, multiply, and has 0/1.
39pub trait Num:
40    Copy + PartialEq + Debug + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self>
41{
42    /// Additive identity.
43    fn zero() -> Self;
44    /// Multiplicative identity.
45    fn one() -> Self;
46    /// Convert from a signed integer (lossy for floats below 2^53).
47    fn from_i64(v: i64) -> Self;
48}
49
50/// Signed numbers: negation, absolute value, sign.
51pub trait Signed: Num + Neg<Output = Self> + PartialOrd {
52    /// Absolute value.
53    fn abs(self) -> Self;
54    /// -1, 0, or +1.
55    fn signum(self) -> Self;
56    /// Returns `true` if the number is negative.
57    fn is_negative(self) -> bool;
58}
59
60/// Division ring: everything [`Num`] is, plus division and reciprocal.
61pub trait Field: Num + Div<Output = Self> + Rem<Output = Self> {
62    /// Divide self by other.
63    fn div(self, other: Self) -> Self;
64    /// `1 / self`.
65    fn reciprocal(self) -> Self;
66}
67
68/// Core real number operations: conversion, powers, rounding, min/max.
69///
70/// Implemented for `f32` and `f64`. This is the minimal trait bound needed
71/// by most generic math code.
72pub trait Real: Num + Signed + Div<Output = Self> + Rem<Output = Self> + PartialOrd {
73    /// Convert from `f64`.
74    fn from_f64(v: f64) -> Self;
75    /// Convert to `f64`.
76    fn to_f64(self) -> f64;
77    /// Machine epsilon for this type.
78    fn epsilon() -> Self;
79
80    /// Square root.
81    fn sqrt(self) -> Self;
82    /// Cube root.
83    fn cbrt(self) -> Self;
84    /// Raise to a floating-point power.
85    fn powf(self, e: Self) -> Self;
86    /// Raise to an integer power.
87    fn powi(self, e: i32) -> Self;
88    /// Reciprocal: `1 / self`.
89    fn recip(self) -> Self;
90
91    /// Floor: largest integer <= self.
92    fn floor(self) -> Self;
93    /// Ceiling: smallest integer >= self.
94    fn ceil(self) -> Self;
95    /// Round to nearest integer.
96    fn round(self) -> Self;
97    /// Truncate toward zero.
98    fn trunc(self) -> Self;
99    /// Fractional part: `self - floor(self)`.
100    fn fract(self) -> Self;
101
102    /// Copy the sign of `other` to the magnitude of `self`.
103    fn copysign(self, other: Self) -> Self;
104    /// `max(self - other, 0)`.
105    fn abs_sub(self, other: Self) -> Self;
106
107    /// Minimum of self and other.
108    fn min(self, other: Self) -> Self;
109    /// Maximum of self and other.
110    fn max(self, other: Self) -> Self;
111}
112
113/// Transcendental functions: exp, log, and their variants.
114pub trait Transcendental: Real {
115    /// `e^self`.
116    fn exp(self) -> Self;
117    /// `e^self - 1`, accurate for small `self`.
118    fn exp_m1(self) -> Self;
119    /// Natural logarithm.
120    fn ln(self) -> Self;
121    /// `ln(1 + self)`, accurate for small `self`.
122    fn ln_1p(self) -> Self;
123    /// Logarithm with arbitrary base.
124    fn log(self, base: Self) -> Self;
125    /// Log base 10.
126    fn log10(self) -> Self;
127    /// Log base 2.
128    fn log2(self) -> Self;
129}
130
131/// Trigonometric functions (radians).
132pub trait Trig: Real {
133    /// Sine.
134    fn sin(self) -> Self;
135    /// Cosine.
136    fn cos(self) -> Self;
137    /// Tangent.
138    fn tan(self) -> Self;
139    /// Arcsine.
140    fn asin(self) -> Self;
141    /// Arccosine.
142    fn acos(self) -> Self;
143    /// Arctangent.
144    fn atan(self) -> Self;
145    /// Two-argument arctangent: `atan(self / other)`.
146    fn atan2(self, other: Self) -> Self;
147    /// Sine and cosine simultaneously.
148    fn sin_cos(self) -> (Self, Self);
149    /// Hypotenuse: `sqrt(self^2 + other^2)`.
150    fn hypot(self, other: Self) -> Self;
151}
152
153/// Hyperbolic functions.
154pub trait Hyperbolic: Real {
155    /// Hyperbolic sine.
156    fn sinh(self) -> Self;
157    /// Hyperbolic cosine.
158    fn cosh(self) -> Self;
159    /// Hyperbolic tangent.
160    fn tanh(self) -> Self;
161    /// Inverse hyperbolic sine.
162    fn asinh(self) -> Self;
163    /// Inverse hyperbolic cosine.
164    fn acosh(self) -> Self;
165    /// Inverse hyperbolic tangent.
166    fn atanh(self) -> Self;
167}
168
169/// Floating-point classification.
170pub trait FloatClass: Real {
171    /// Returns `true` if self is neither infinite nor NaN.
172    fn is_finite(self) -> bool;
173    /// Returns `true` if self is NaN.
174    fn is_nan(self) -> bool;
175    /// Returns `true` if self is positive or negative infinity.
176    fn is_infinite(self) -> bool;
177    /// Returns `true` if self is neither zero, infinite, subnormal, nor NaN.
178    fn is_normal(self) -> bool;
179    /// Returns `true` if self is subnormal (denormal).
180    fn is_subnormal(self) -> bool;
181    /// Returns `true` if the sign bit is negative.
182    fn is_sign_negative(self) -> bool;
183    /// Returns `true` if the sign bit is positive (or zero).
184    fn is_sign_positive(self) -> bool;
185}
186
187/// Backward-compat supertrait combining all sub-traits.
188///
189/// Prefer using specific sub-traits (`Trig`, `Hyperbolic`, etc.) in new code.
190pub trait RealFull:
191    Real + Transcendental + Trig + Hyperbolic + FloatClass
192{
193}
194impl<T: Real + Transcendental + Trig + Hyperbolic + FloatClass> RealFull for T {}
195
196/// Normed type: has an absolute value satisfying the triangle inequality.
197///
198/// `norm(x) >= 0`, `norm(x) == 0` iff `x == 0`, `norm(x + y) <= norm(x) + norm(y)`.
199pub trait Normed: Signed {
200    /// The norm (absolute value) of this value.
201    fn norm(self) -> Self;
202}
203
204macro_rules! impl_num_int {
205    ($($t:ty),* $(,)?) => {$(
206        impl Num for $t {
207            fn zero() -> Self { 0 }
208            fn one() -> Self { 1 }
209            fn from_i64(v: i64) -> Self { v as $t }
210        }
211    )*};
212}
213impl_num_int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
214
215macro_rules! impl_num_float {
216    ($($t:ty),* $(,)?) => {$(
217        impl Num for $t {
218            fn zero() -> Self { 0.0 }
219            fn one() -> Self { 1.0 }
220            fn from_i64(v: i64) -> Self { v as $t }
221        }
222    )*};
223}
224impl_num_float!(f32, f64);
225
226macro_rules! impl_signed_int {
227    ($($t:ty),* $(,)?) => {$(
228        impl Signed for $t {
229            fn abs(self) -> Self { self.abs() }
230            fn signum(self) -> Self { self.signum() }
231            fn is_negative(self) -> bool { self < 0 }
232        }
233    )*};
234}
235impl_signed_int!(i8, i16, i32, i64, isize);
236
237macro_rules! impl_signed_float {
238    ($($t:ty),* $(,)?) => {$(
239        impl Signed for $t {
240            fn abs(self) -> Self { self.abs() }
241            fn signum(self) -> Self { self.signum() }
242            fn is_negative(self) -> bool { self.is_sign_negative() }
243        }
244    )*};
245}
246impl_signed_float!(f32, f64);
247
248macro_rules! impl_field_float {
249    ($($t:ty),* $(,)?) => {$(
250        impl Field for $t {
251            fn div(self, other: Self) -> Self { self / other }
252            fn reciprocal(self) -> Self { 1.0 / self }
253        }
254    )*};
255}
256impl_field_float!(f32, f64);
257
258#[cfg(feature = "std")]
259macro_rules! impl_real_core {
260    ($($t:ty),* $(,)?) => {$(
261        impl Real for $t {
262            fn from_f64(v: f64) -> Self { v as $t }
263            fn to_f64(self) -> f64 { f64::from(self) }
264            fn epsilon() -> Self { Self::from_f64($crate::precision::EPS) }
265            fn sqrt(self) -> Self { self.sqrt() }
266            fn cbrt(self) -> Self { self.cbrt() }
267            fn powf(self, e: Self) -> Self { self.powf(e) }
268            fn powi(self, e: i32) -> Self { self.powi(e) }
269            fn recip(self) -> Self { self.recip() }
270            fn floor(self) -> Self { self.floor() }
271            fn ceil(self) -> Self { self.ceil() }
272            fn round(self) -> Self { self.round() }
273            fn trunc(self) -> Self { self.trunc() }
274            fn fract(self) -> Self { self.fract() }
275            fn copysign(self, other: Self) -> Self { self.copysign(other) }
276            fn abs_sub(self, other: Self) -> Self { (self - other).max(Self::zero()) }
277            fn min(self, other: Self) -> Self { self.min(other) }
278            fn max(self, other: Self) -> Self { self.max(other) }
279        }
280    )*};
281}
282
283#[cfg(feature = "std")]
284macro_rules! impl_transcendental {
285    ($($t:ty),* $(,)?) => {$(
286        impl Transcendental for $t {
287            fn exp(self) -> Self { self.exp() }
288            fn exp_m1(self) -> Self { self.exp_m1() }
289            fn ln(self) -> Self { self.ln() }
290            fn ln_1p(self) -> Self { self.ln_1p() }
291            fn log(self, base: Self) -> Self { self.log(base) }
292            fn log10(self) -> Self { self.log10() }
293            fn log2(self) -> Self { self.log2() }
294        }
295    )*};
296}
297
298#[cfg(feature = "std")]
299macro_rules! impl_trig {
300    ($($t:ty),* $(,)?) => {$(
301        impl Trig for $t {
302            fn sin(self) -> Self { self.sin() }
303            fn cos(self) -> Self { self.cos() }
304            fn tan(self) -> Self { self.tan() }
305            fn asin(self) -> Self { self.asin() }
306            fn acos(self) -> Self { self.acos() }
307            fn atan(self) -> Self { self.atan() }
308            fn atan2(self, other: Self) -> Self { self.atan2(other) }
309            fn sin_cos(self) -> (Self, Self) { self.sin_cos() }
310            fn hypot(self, other: Self) -> Self { self.hypot(other) }
311        }
312    )*};
313}
314
315#[cfg(feature = "std")]
316macro_rules! impl_hyperbolic {
317    ($($t:ty),* $(,)?) => {$(
318        impl Hyperbolic for $t {
319            fn sinh(self) -> Self { self.sinh() }
320            fn cosh(self) -> Self { self.cosh() }
321            fn tanh(self) -> Self { self.tanh() }
322            fn asinh(self) -> Self { self.asinh() }
323            fn acosh(self) -> Self { self.acosh() }
324            fn atanh(self) -> Self { self.atanh() }
325        }
326    )*};
327}
328
329#[cfg(feature = "std")]
330macro_rules! impl_float_class {
331    ($($t:ty),* $(,)?) => {$(
332        impl FloatClass for $t {
333            fn is_finite(self) -> bool { self.is_finite() }
334            fn is_nan(self) -> bool { self.is_nan() }
335            fn is_infinite(self) -> bool { self.is_infinite() }
336            fn is_normal(self) -> bool { self.is_normal() }
337            fn is_subnormal(self) -> bool { self.is_subnormal() }
338            fn is_sign_negative(self) -> bool { self.is_sign_negative() }
339            fn is_sign_positive(self) -> bool { self.is_sign_positive() }
340        }
341    )*};
342}
343
344// std impl: uses hardware-accelerated float methods
345#[cfg(feature = "std")]
346mod real_impls {
347    use super::{Num, Real, Transcendental, Trig, Hyperbolic, FloatClass};
348    impl_real_core!(f32, f64);
349    impl_transcendental!(f32, f64);
350    impl_trig!(f32, f64);
351    impl_hyperbolic!(f32, f64);
352    impl_float_class!(f32, f64);
353}
354
355// no_std + libm impl: uses libm software implementations
356#[cfg(all(not(feature = "std"), feature = "libm"))]
357mod real_impls {
358    use super::{Num, Real, Transcendental, Trig, Hyperbolic, FloatClass};
359
360    macro_rules! impl_real_core_libm {
361        ($($t:ty),* $(,)?) => {$(
362            impl Real for $t {
363                fn from_f64(v: f64) -> Self { v as $t }
364                fn to_f64(self) -> f64 { f64::from(self) }
365                fn epsilon() -> Self { Self::from_f64($crate::precision::EPS) }
366                fn sqrt(self) -> Self { crate::libm_fallback::sqrt(self as f64) as $t }
367                fn cbrt(self) -> Self { crate::libm_fallback::cbrt(self as f64) as $t }
368                fn powf(self, e: Self) -> Self { crate::libm_fallback::powf(self as f64, e as f64) as $t }
369                fn powi(self, e: i32) -> Self { crate::libm_fallback::powi(self as f64, e) as $t }
370                fn recip(self) -> Self { 1.0 / self }
371                fn floor(self) -> Self { crate::libm_fallback::floor(self as f64) as $t }
372                fn ceil(self) -> Self { crate::libm_fallback::ceil(self as f64) as $t }
373                fn round(self) -> Self { crate::libm_fallback::round(self as f64) as $t }
374                fn trunc(self) -> Self { crate::libm_fallback::trunc(self as f64) as $t }
375                fn fract(self) -> Self { self - crate::libm_fallback::floor(self as f64) as $t }
376                fn copysign(self, other: Self) -> Self {
377                    if other.is_sign_negative() { -self.abs() } else { self.abs() }
378                }
379                fn abs_sub(self, other: Self) -> Self { (self - other).max(Self::zero()) }
380                fn min(self, other: Self) -> Self { if self < other { self } else { other } }
381                fn max(self, other: Self) -> Self { if self > other { self } else { other } }
382            }
383        )*};
384    }
385
386    macro_rules! impl_transcendental_libm {
387        ($($t:ty),* $(,)?) => {$(
388            impl Transcendental for $t {
389                fn exp(self) -> Self { crate::libm_fallback::exp(self as f64) as $t }
390                fn exp_m1(self) -> Self { crate::libm_fallback::exp_m1(self as f64) as $t }
391                fn ln(self) -> Self { crate::libm_fallback::ln(self as f64) as $t }
392                fn ln_1p(self) -> Self { crate::libm_fallback::ln_1p(self as f64) as $t }
393                fn log(self, base: Self) -> Self { crate::libm_fallback::log(self as f64, base as f64) as $t }
394                fn log10(self) -> Self { crate::libm_fallback::log10(self as f64) as $t }
395                fn log2(self) -> Self { crate::libm_fallback::log2(self as f64) as $t }
396            }
397        )*};
398    }
399
400    macro_rules! impl_trig_libm {
401        ($($t:ty),* $(,)?) => {$(
402            impl Trig for $t {
403                fn sin(self) -> Self { crate::libm_fallback::sin(self as f64) as $t }
404                fn cos(self) -> Self { crate::libm_fallback::cos(self as f64) as $t }
405                fn tan(self) -> Self { crate::libm_fallback::tan(self as f64) as $t }
406                fn asin(self) -> Self { crate::libm_fallback::asin(self as f64) as $t }
407                fn acos(self) -> Self { crate::libm_fallback::acos(self as f64) as $t }
408                fn atan(self) -> Self { crate::libm_fallback::atan(self as f64) as $t }
409                fn atan2(self, other: Self) -> Self { crate::libm_fallback::atan2(self as f64, other as f64) as $t }
410                fn sin_cos(self) -> (Self, Self) {
411                    let (s, c) = crate::libm_fallback::sin_cos(self as f64);
412                    (s as $t, c as $t)
413                }
414                fn hypot(self, other: Self) -> Self { crate::libm_fallback::hypot(self as f64, other as f64) as $t }
415            }
416        )*};
417    }
418
419    macro_rules! impl_hyperbolic_libm {
420        ($($t:ty),* $(,)?) => {$(
421            impl Hyperbolic for $t {
422                fn sinh(self) -> Self { crate::libm_fallback::sinh(self as f64) as $t }
423                fn cosh(self) -> Self { crate::libm_fallback::cosh(self as f64) as $t }
424                fn tanh(self) -> Self { crate::libm_fallback::tanh(self as f64) as $t }
425                fn asinh(self) -> Self { crate::libm_fallback::asinh(self as f64) as $t }
426                fn acosh(self) -> Self { crate::libm_fallback::acosh(self as f64) as $t }
427                fn atanh(self) -> Self { crate::libm_fallback::atanh(self as f64) as $t }
428            }
429        )*};
430    }
431
432    macro_rules! impl_float_class_libm {
433        ($($t:ty),* $(,)?) => {$(
434            impl FloatClass for $t {
435                fn is_finite(self) -> bool { self.is_finite() }
436                fn is_nan(self) -> bool { self.is_nan() }
437                fn is_infinite(self) -> bool { self.is_infinite() }
438                fn is_normal(self) -> bool { self.is_normal() }
439                fn is_subnormal(self) -> bool { self.is_finite() && self != 0.0 && self.abs() < Self::MIN_POSITIVE }
440                fn is_sign_negative(self) -> bool { self.is_sign_negative() }
441                fn is_sign_positive(self) -> bool { self.is_sign_positive() }
442            }
443        )*};
444    }
445
446    impl_real_core_libm!(f32, f64);
447    impl_transcendental_libm!(f32, f64);
448    impl_trig_libm!(f32, f64);
449    impl_hyperbolic_libm!(f32, f64);
450    impl_float_class_libm!(f32, f64);
451}
452
453impl Normed for f32 {
454    fn norm(self) -> Self {
455        self.abs()
456    }
457}
458impl Normed for f64 {
459    fn norm(self) -> Self {
460        self.abs()
461    }
462}
463impl Normed for i8 {
464    fn norm(self) -> Self {
465        self.abs()
466    }
467}
468impl Normed for i16 {
469    fn norm(self) -> Self {
470        self.abs()
471    }
472}
473impl Normed for i32 {
474    fn norm(self) -> Self {
475        self.abs()
476    }
477}
478impl Normed for i64 {
479    fn norm(self) -> Self {
480        self.abs()
481    }
482}
483impl Normed for isize {
484    fn norm(self) -> Self {
485        self.abs()
486    }
487}
488
489#[cfg(test)]
490#[allow(clippy::suboptimal_flops, clippy::imprecise_flops)]
491mod tests {
492    use super::{Field, Num, Real};
493
494    #[test]
495    fn identity_laws() {
496        for a in 0..100i64 {
497            assert_eq!(a + <i64 as Num>::zero(), a);
498            assert_eq!(a * <i64 as Num>::one(), a);
499        }
500    }
501
502    #[test]
503    fn signed_abs() {
504        assert_eq!((-5i32).abs(), 5);
505        assert_eq!((-2.5f64).abs(), 2.5);
506        assert_eq!((-2.5f64).signum(), -1.0);
507    }
508
509    #[test]
510    fn real_ops() {
511        let x = 9.0f64;
512        assert_eq!(x.sqrt(), 3.0);
513        assert_eq!(x.cbrt(), 2.080083823051904);
514        assert_eq!(x.powi(2), 81.0);
515        assert_eq!(x.powf(0.5), 3.0);
516        assert_eq!(x.ln(), 2.0 * 3.0f64.ln());
517        assert!((1.0f64.sin().powi(2) + 1.0f64.cos().powi(2) - 1.0).abs() < 1e-12);
518        assert!((0.5f64.asin() - 0.5f64.asin()).abs() < 1e-12);
519        assert!((0.5f64.acos() - 0.5f64.acos()).abs() < 1e-12);
520        assert!((1.0f64.atan() - core::f64::consts::FRAC_PI_4).abs() < 1e-12);
521        assert!((1.0f64.atan2(1.0) - core::f64::consts::FRAC_PI_4).abs() < 1e-12);
522        assert!((1.0f64.cosh().powi(2) - 1.0f64.sinh().powi(2) - 1.0).abs() < 1e-12);
523        assert!((0.5f64.tanh() - 0.5f64.tanh()).abs() < 1e-12);
524        assert!((1.0f64.asinh() - 1.0f64.asinh()).abs() < 1e-12);
525        assert!((0.001f64.exp_m1() - (0.001f64.exp() - 1.0)).abs() < 1e-15);
526        assert!((0.001f64.ln_1p() - 0.001f64.ln_1p()).abs() < 1e-15);
527        let (s, c) = 0.0f64.sin_cos();
528        assert!((s - 0.0f64.sin()).abs() < 1e-15);
529        assert!((c - 0.0f64.cos()).abs() < 1e-15);
530        assert!((3.0f64.hypot(4.0) - 5.0).abs() < 1e-12);
531        assert_eq!(3.0f64.copysign(-1.0), -3.0);
532        assert_eq!(Real::abs_sub(5.0f64, 3.0), 2.0);
533        assert_eq!(Real::abs_sub(4.0f64, 5.0), 0.0);
534        assert_eq!(4.0f64.recip(), 0.25);
535        assert!((8.0f64.log2() - 3.0).abs() < 1e-12);
536        assert_eq!(2.7f64.trunc(), 2.0);
537        assert!((2.7f64.fract() - 0.7).abs() < 1e-12);
538        assert_eq!((-2.5f64).signum(), -1.0);
539        assert!(1.0f64.is_normal());
540        assert!(!0.0f64.is_normal());
541        assert!(1.0f64.is_sign_positive());
542        assert!((-0.0f64).is_sign_negative());
543    }
544
545    #[test]
546    fn field_reciprocal() {
547        assert_eq!(4.0f64.reciprocal(), 0.25);
548        assert_eq!(Field::div(10.0f32, 4.0), 2.5);
549    }
550}