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   │   f32 / f64 blanket impl
24//!       └─────────┘
25//! ```
26
27use core::fmt::Debug;
28use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
29
30/// Foundation: anything that can add, subtract, multiply, and has 0/1.
31pub trait Num:
32    Copy + PartialEq + Debug + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self>
33{
34    /// Additive identity.
35    fn zero() -> Self;
36    /// Multiplicative identity.
37    fn one() -> Self;
38    /// Convert from a signed integer (lossy for floats below 2^53).
39    fn from_i64(v: i64) -> Self;
40}
41
42/// Signed numbers: negation, absolute value, sign.
43pub trait Signed: Num + Neg<Output = Self> + PartialOrd {
44    /// Absolute value.
45    fn abs(self) -> Self;
46    /// -1, 0, or +1.
47    fn signum(self) -> Self;
48    /// Returns `true` if the number is negative.
49    fn is_negative(self) -> bool;
50}
51
52/// Division ring: everything [`Num`] is, plus division and reciprocal.
53pub trait Field: Num + Div<Output = Self> + Rem<Output = Self> {
54    /// Divide self by other.
55    fn div(self, other: Self) -> Self;
56    /// `1 / self`.
57    fn reciprocal(self) -> Self;
58}
59
60/// Real numbers: transcendental functions, powers, rounding, classification.
61///
62/// Implemented for `f32` and `f64`. Functions take radians unless a `*_deg`
63/// variant exists.
64pub trait Real: Num + Signed + Div<Output = Self> + Rem<Output = Self> + PartialOrd {
65    /// Convert from `f64`.
66    fn from_f64(v: f64) -> Self;
67    /// Convert to `f64`.
68    fn to_f64(self) -> f64;
69
70    /// Square root.
71    fn sqrt(self) -> Self;
72    /// Cube root.
73    fn cbrt(self) -> Self;
74    /// Raise to a floating-point power.
75    fn powf(self, e: Self) -> Self;
76    /// Raise to an integer power.
77    fn powi(self, e: i32) -> Self;
78    /// `e^self`.
79    fn exp(self) -> Self;
80    /// `e^self - 1`, accurate for small `self`.
81    fn exp_m1(self) -> Self;
82    /// Natural logarithm.
83    fn ln(self) -> Self;
84    /// `ln(1 + self)`, accurate for small `self`.
85    fn ln_1p(self) -> Self;
86    /// Logarithm with arbitrary base.
87    fn log(self, base: Self) -> Self;
88    /// Log base 10.
89    fn log10(self) -> Self;
90    /// Log base 2.
91    fn log2(self) -> Self;
92
93    /// Sine (radians).
94    fn sin(self) -> Self;
95    /// Cosine (radians).
96    fn cos(self) -> Self;
97    /// Tangent (radians).
98    fn tan(self) -> Self;
99    /// Arcsine.
100    fn asin(self) -> Self;
101    /// Arccosine.
102    fn acos(self) -> Self;
103    /// Arctangent.
104    fn atan(self) -> Self;
105    /// Two-argument arctangent: `atan(self / other)`.
106    fn atan2(self, other: Self) -> Self;
107
108    /// Hyperbolic sine.
109    fn sinh(self) -> Self;
110    /// Hyperbolic cosine.
111    fn cosh(self) -> Self;
112    /// Hyperbolic tangent.
113    fn tanh(self) -> Self;
114    /// Inverse hyperbolic sine.
115    fn asinh(self) -> Self;
116    /// Inverse hyperbolic cosine.
117    fn acosh(self) -> Self;
118    /// Inverse hyperbolic tangent.
119    fn atanh(self) -> Self;
120
121    /// Sine and cosine simultaneously.
122    fn sin_cos(self) -> (Self, Self);
123    /// Hypotenuse: `sqrt(self^2 + other^2)`.
124    fn hypot(self, other: Self) -> Self;
125    /// Copy the sign of `other` to the magnitude of `self`.
126    fn copysign(self, other: Self) -> Self;
127    /// `max(self - other, 0)`.
128    fn abs_sub(self, other: Self) -> Self;
129    /// Reciprocal: `1 / self`.
130    fn recip(self) -> Self;
131
132    /// Floor: largest integer <= self.
133    fn floor(self) -> Self;
134    /// Ceiling: smallest integer >= self.
135    fn ceil(self) -> Self;
136    /// Round to nearest integer.
137    fn round(self) -> Self;
138    /// Truncate toward zero.
139    fn trunc(self) -> Self;
140    /// Fractional part: `self - floor(self)`.
141    fn fract(self) -> Self;
142
143    /// Minimum of self and other.
144    fn min(self, other: Self) -> Self;
145    /// Maximum of self and other.
146    fn max(self, other: Self) -> Self;
147
148    /// Returns `true` if self is neither infinite nor NaN.
149    fn is_finite(self) -> bool;
150    /// Returns `true` if self is NaN.
151    fn is_nan(self) -> bool;
152    /// Returns `true` if self is positive or negative infinity.
153    fn is_infinite(self) -> bool;
154    /// Returns `true` if self is neither zero, infinite, subnormal, nor NaN.
155    fn is_normal(self) -> bool;
156    /// Returns `true` if self is subnormal (denormal).
157    fn is_subnormal(self) -> bool;
158    /// Returns `true` if the sign bit is negative.
159    fn is_sign_negative(self) -> bool;
160    /// Returns `true` if the sign bit is positive (or zero).
161    fn is_sign_positive(self) -> bool;
162
163    /// Returns -1, 0, or +1.
164    fn signum(self) -> Self;
165}
166
167/// Normed type: has an absolute value satisfying the triangle inequality.
168///
169/// `norm(x) >= 0`, `norm(x) == 0` iff `x == 0`, `norm(x + y) <= norm(x) + norm(y)`.
170pub trait Normed: Signed {
171    /// The norm (absolute value) of this value.
172    fn norm(self) -> Self;
173}
174
175macro_rules! impl_num_int {
176    ($($t:ty),* $(,)?) => {$(
177        impl Num for $t {
178            fn zero() -> Self { 0 }
179            fn one() -> Self { 1 }
180            fn from_i64(v: i64) -> Self { v as $t }
181        }
182    )*};
183}
184impl_num_int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
185
186macro_rules! impl_num_float {
187    ($($t:ty),* $(,)?) => {$(
188        impl Num for $t {
189            fn zero() -> Self { 0.0 }
190            fn one() -> Self { 1.0 }
191            fn from_i64(v: i64) -> Self { v as $t }
192        }
193    )*};
194}
195impl_num_float!(f32, f64);
196
197macro_rules! impl_signed_int {
198    ($($t:ty),* $(,)?) => {$(
199        impl Signed for $t {
200            fn abs(self) -> Self { self.abs() }
201            fn signum(self) -> Self { self.signum() }
202            fn is_negative(self) -> bool { self < 0 }
203        }
204    )*};
205}
206impl_signed_int!(i8, i16, i32, i64, isize);
207
208macro_rules! impl_signed_float {
209    ($($t:ty),* $(,)?) => {$(
210        impl Signed for $t {
211            fn abs(self) -> Self { self.abs() }
212            fn signum(self) -> Self { self.signum() }
213            fn is_negative(self) -> bool { self.is_sign_negative() }
214        }
215    )*};
216}
217impl_signed_float!(f32, f64);
218
219macro_rules! impl_field_float {
220    ($($t:ty),* $(,)?) => {$(
221        impl Field for $t {
222            fn div(self, other: Self) -> Self { self / other }
223            fn reciprocal(self) -> Self { 1.0 / self }
224        }
225    )*};
226}
227impl_field_float!(f32, f64);
228
229macro_rules! impl_real {
230    ($($t:ty),* $(,)?) => {$(
231        impl Real for $t {
232            fn from_f64(v: f64) -> Self { v as $t }
233            fn to_f64(self) -> f64 { f64::from(self) }
234            fn sqrt(self) -> Self { self.sqrt() }
235            fn cbrt(self) -> Self { self.cbrt() }
236            fn powf(self, e: Self) -> Self { self.powf(e) }
237            fn powi(self, e: i32) -> Self { self.powi(e) }
238            fn exp(self) -> Self { self.exp() }
239            fn exp_m1(self) -> Self { self.exp_m1() }
240            fn ln(self) -> Self { self.ln() }
241            fn ln_1p(self) -> Self { self.ln_1p() }
242            fn log(self, base: Self) -> Self { self.log(base) }
243            fn log10(self) -> Self { self.log10() }
244            fn log2(self) -> Self { self.log2() }
245            fn sin(self) -> Self { self.sin() }
246            fn cos(self) -> Self { self.cos() }
247            fn tan(self) -> Self { self.tan() }
248            fn asin(self) -> Self { self.asin() }
249            fn acos(self) -> Self { self.acos() }
250            fn atan(self) -> Self { self.atan() }
251            fn atan2(self, other: Self) -> Self { self.atan2(other) }
252            fn sinh(self) -> Self { self.sinh() }
253            fn cosh(self) -> Self { self.cosh() }
254            fn tanh(self) -> Self { self.tanh() }
255            fn asinh(self) -> Self { self.asinh() }
256            fn acosh(self) -> Self { self.acosh() }
257            fn atanh(self) -> Self { self.atanh() }
258            fn sin_cos(self) -> (Self, Self) { self.sin_cos() }
259            fn hypot(self, other: Self) -> Self { self.hypot(other) }
260            fn copysign(self, other: Self) -> Self { self.copysign(other) }
261            fn abs_sub(self, other: Self) -> Self { (self - other).max(Self::zero()) }
262            fn recip(self) -> Self { self.recip() }
263            fn floor(self) -> Self { self.floor() }
264            fn ceil(self) -> Self { self.ceil() }
265            fn round(self) -> Self { self.round() }
266            fn trunc(self) -> Self { self.trunc() }
267            fn fract(self) -> Self { self.fract() }
268            fn min(self, other: Self) -> Self { self.min(other) }
269            fn max(self, other: Self) -> Self { self.max(other) }
270            fn is_finite(self) -> bool { self.is_finite() }
271            fn is_nan(self) -> bool { self.is_nan() }
272            fn is_infinite(self) -> bool { self.is_infinite() }
273            fn is_normal(self) -> bool { self.is_normal() }
274            fn is_subnormal(self) -> bool { self.is_subnormal() }
275            fn is_sign_negative(self) -> bool { self.is_sign_negative() }
276            fn is_sign_positive(self) -> bool { self.is_sign_positive() }
277            fn signum(self) -> Self { self.signum() }
278        }
279    )*};
280}
281impl_real!(f32, f64);
282
283impl Normed for f32 {
284    fn norm(self) -> Self {
285        self.abs()
286    }
287}
288impl Normed for f64 {
289    fn norm(self) -> Self {
290        self.abs()
291    }
292}
293impl Normed for i8 {
294    fn norm(self) -> Self {
295        self.abs()
296    }
297}
298impl Normed for i16 {
299    fn norm(self) -> Self {
300        self.abs()
301    }
302}
303impl Normed for i32 {
304    fn norm(self) -> Self {
305        self.abs()
306    }
307}
308impl Normed for i64 {
309    fn norm(self) -> Self {
310        self.abs()
311    }
312}
313impl Normed for isize {
314    fn norm(self) -> Self {
315        self.abs()
316    }
317}
318
319#[cfg(test)]
320#[allow(clippy::suboptimal_flops, clippy::imprecise_flops)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn identity_laws() {
326        for a in 0..100i64 {
327            assert_eq!(a + <i64 as Num>::zero(), a);
328            assert_eq!(a * <i64 as Num>::one(), a);
329        }
330    }
331
332    #[test]
333    fn signed_abs() {
334        assert_eq!((-5i32).abs(), 5);
335        assert_eq!((-2.5f64).abs(), 2.5);
336        assert_eq!((-2.5f64).signum(), -1.0);
337    }
338
339    #[test]
340    fn real_ops() {
341        let x = 9.0f64;
342        assert_eq!(x.sqrt(), 3.0);
343        assert_eq!(x.cbrt(), 2.080083823051904);
344        assert_eq!(x.powi(2), 81.0);
345        assert_eq!(x.powf(0.5), 3.0);
346        assert_eq!(x.ln(), 2.0 * 3.0f64.ln());
347        assert!((1.0f64.sin().powi(2) + 1.0f64.cos().powi(2) - 1.0).abs() < 1e-12);
348        assert!((0.5f64.asin() - 0.5f64.asin()).abs() < 1e-12);
349        assert!((0.5f64.acos() - 0.5f64.acos()).abs() < 1e-12);
350        assert!((1.0f64.atan() - core::f64::consts::FRAC_PI_4).abs() < 1e-12);
351        assert!((1.0f64.atan2(1.0) - core::f64::consts::FRAC_PI_4).abs() < 1e-12);
352        assert!((1.0f64.cosh().powi(2) - 1.0f64.sinh().powi(2) - 1.0).abs() < 1e-12);
353        assert!((0.5f64.tanh() - 0.5f64.tanh()).abs() < 1e-12);
354        assert!((1.0f64.asinh() - 1.0f64.asinh()).abs() < 1e-12);
355        assert!((0.001f64.exp_m1() - (0.001f64.exp() - 1.0)).abs() < 1e-15);
356        assert!((0.001f64.ln_1p() - 0.001f64.ln_1p()).abs() < 1e-15);
357        let (s, c) = 0.0f64.sin_cos();
358        assert!((s - 0.0f64.sin()).abs() < 1e-15);
359        assert!((c - 0.0f64.cos()).abs() < 1e-15);
360        assert!((3.0f64.hypot(4.0) - 5.0).abs() < 1e-12);
361        assert_eq!(3.0f64.copysign(-1.0), -3.0);
362        assert_eq!(Real::abs_sub(5.0f64, 3.0), 2.0);
363        assert_eq!(Real::abs_sub(4.0f64, 5.0), 0.0);
364        assert_eq!(4.0f64.recip(), 0.25);
365        assert!((8.0f64.log2() - 3.0).abs() < 1e-12);
366        assert_eq!(2.7f64.trunc(), 2.0);
367        assert!((2.7f64.fract() - 0.7).abs() < 1e-12);
368        assert_eq!((-2.5f64).signum(), -1.0);
369        assert!(1.0f64.is_normal());
370        assert!(!0.0f64.is_normal());
371        assert!(1.0f64.is_sign_positive());
372        assert!((-0.0f64).is_sign_negative());
373    }
374
375    #[test]
376    fn field_reciprocal() {
377        assert_eq!(4.0f64.reciprocal(), 0.25);
378        assert_eq!(Field::div(10.0f32, 4.0), 2.5);
379    }
380}