Skip to main content

mathverse_core/
ops.rs

1//! Generic scalar operations over [`Real`].
2//!
3//! All functions in this module are generic over the [`Real`] trait and work
4//! with both `f32` and `f64`.
5
6use crate::constants::{DEG_TO_GRAD, GRAD_TO_DEG, GRAD_TO_RAD};
7use crate::traits::{Num, Real};
8use core::ops::Rem;
9
10/// Clamp `x` into `[lo, hi]`.
11///
12/// # Examples
13///
14/// ```
15/// use mathverse_core::ops::clamp;
16///
17/// assert_eq!(clamp(5.0, 0.0, 3.0), 3.0);
18/// assert_eq!(clamp(1.0, 0.0, 3.0), 1.0);
19/// ```
20#[must_use]
21#[inline]
22pub fn clamp<T: Real>(x: T, lo: T, hi: T) -> T {
23    x.max(lo).min(hi)
24}
25
26/// Linear interpolation between `a` and `b`.
27///
28/// `t = 0` returns `a`, `t = 1` returns `b`. Values of `t` outside `[0, 1]`
29/// extrapolate beyond the interval.
30///
31/// # Examples
32///
33/// ```
34/// use mathverse_core::ops::lerp;
35///
36/// assert_eq!(lerp(0.0, 10.0, 0.25), 2.5);
37/// assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
38/// assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
39/// ```
40#[must_use]
41#[inline]
42pub fn lerp<T: Real>(a: T, b: T, t: T) -> T {
43    a + (b - a) * t
44}
45
46/// Inverse linear interpolation: find `t` such that `lerp(a, b, t) == x`.
47///
48/// Returns `0` when `a == b`.
49///
50/// # Examples
51///
52/// ```
53/// use mathverse_core::ops::lerp_inv;
54///
55/// assert_eq!(lerp_inv(5.0, 0.0, 10.0), 0.5);
56/// assert_eq!(lerp_inv(0.0, 0.0, 10.0), 0.0);
57/// ```
58#[must_use]
59#[inline]
60pub fn lerp_inv<T: Real>(x: T, a: T, b: T) -> T {
61    let d = b - a;
62    if d == T::zero() {
63        T::zero()
64    } else {
65        (x - a) / d
66    }
67}
68
69/// Smoothstep: C1 interpolation with zero derivatives at both ends.
70///
71/// `x` assumed within `[0, 1]`. Formula: `3t^2 - 2t^3`.
72///
73/// # Examples
74///
75/// ```
76/// use mathverse_core::ops::smoothstep;
77///
78/// assert_eq!(smoothstep(0.0), 0.0);
79/// assert_eq!(smoothstep(0.5), 0.5);
80/// assert_eq!(smoothstep(1.0), 1.0);
81/// ```
82#[must_use]
83#[inline]
84pub fn smoothstep<T: Real>(x: T) -> T {
85    x * x * (T::from_f64(3.0) - T::from_f64(2.0) * x)
86}
87
88/// Smootherstep: C2 interpolation with zero derivatives at both ends.
89///
90/// `t` assumed within `[0, 1]`. Formula: `6t^5 - 15t^4 + 10t^3`.
91///
92/// # Examples
93///
94/// ```
95/// use mathverse_core::ops::smootherstep;
96///
97/// assert_eq!(smootherstep(0.0), 0.0);
98/// assert_eq!(smootherstep(0.5), 0.5);
99/// assert_eq!(smootherstep(1.0), 1.0);
100/// ```
101#[must_use]
102#[inline]
103pub fn smootherstep<T: Real>(x: T) -> T {
104    let x3 = x * x * x;
105    x3 * (x * x * T::from_f64(6.0) - x * T::from_f64(15.0) + T::from_f64(10.0))
106}
107
108/// Fractional part: `x - floor(x)`. Negative inputs give `[0, 1)`.
109///
110/// # Examples
111///
112/// ```
113/// use mathverse_core::ops::fract;
114///
115/// assert!((fract(2.75_f64) - 0.75).abs() < 1e-12);
116/// assert!((fract(-0.25_f64) - 0.75).abs() < 1e-12);
117/// ```
118#[must_use]
119#[inline]
120pub fn fract<T: Real>(x: T) -> T {
121    x - x.floor()
122}
123
124/// Integer part: `floor(x)`.
125///
126/// # Examples
127///
128/// ```
129/// use mathverse_core::ops::integer_part;
130///
131/// assert_eq!(integer_part(2.7), 2.0);
132/// assert_eq!(integer_part(-2.7), -3.0);
133/// ```
134#[must_use]
135#[inline]
136pub fn integer_part<T: Real>(x: T) -> T {
137    x.floor()
138}
139
140/// `n`-th root of `x` (any real `n`).
141///
142/// # Examples
143///
144/// ```
145/// use mathverse_core::ops::nth_root;
146///
147/// assert!((nth_root(27.0_f64, 3) - 3.0).abs() < 1e-12);
148/// assert!((nth_root(16.0_f64, 4) - 2.0).abs() < 1e-12);
149/// ```
150#[must_use]
151pub fn nth_root<T: Real>(x: T, n: i32) -> T {
152    x.powf(T::one() / T::from_f64(f64::from(n)))
153}
154
155/// Cube root of `x`.
156///
157/// # Examples
158///
159/// ```
160/// use mathverse_core::ops::cbrt;
161///
162/// assert!((cbrt(27.0_f64) - 3.0).abs() < 1e-12);
163/// assert!((cbrt(8.0_f64) - 2.0).abs() < 1e-12);
164/// ```
165#[must_use]
166#[inline]
167pub fn cbrt<T: Real>(x: T) -> T {
168    x.powf(T::from_f64(1.0 / 3.0))
169}
170
171/// Overflow-safe `sqrt(a^2 + b^2)`, generic over reals.
172///
173/// # Examples
174///
175/// ```
176/// use mathverse_core::ops::hypot2;
177///
178/// assert_eq!(hypot2(3.0, 4.0), 5.0);
179/// assert_eq!(hypot2(0.0, 0.0), 0.0);
180/// ```
181#[must_use]
182pub fn hypot2<T: Real>(a: T, b: T) -> T {
183    let (a, b) = (a.abs(), b.abs());
184    let m = a.max(b);
185    if m == T::zero() {
186        T::zero()
187    } else {
188        m * (T::one() + (a.min(b) / m).powi(2)).sqrt()
189    }
190}
191
192/// Overflow-safe `sqrt(a^2 + b^2 + c^2)`, generic over reals.
193///
194/// # Examples
195///
196/// ```
197/// use mathverse_core::ops::hypot3;
198///
199/// assert_eq!(hypot3(3.0, 4.0, 12.0), 13.0);
200/// ```
201#[must_use]
202pub fn hypot3<T: Real>(a: T, b: T, c: T) -> T {
203    let m = a.abs().max(b.abs()).max(c.abs());
204    if m == T::zero() {
205        T::zero()
206    } else {
207        let (a, b, c) = (a / m, b / m, c / m);
208        m * (a * a + b * b + c * c).sqrt()
209    }
210}
211
212/// Sum of a slice. Returns `T::zero()` for an empty slice.
213///
214/// # Examples
215///
216/// ```
217/// use mathverse_core::ops::sum;
218///
219/// assert_eq!(sum(&[1, 2, 3, 4]), 10);
220/// assert_eq!(sum::<i32>(&[]), 0);
221/// ```
222#[must_use]
223pub fn sum<T: Num>(xs: &[T]) -> T {
224    xs.iter().copied().fold(T::zero(), |acc, x| acc + x)
225}
226
227/// Product of a slice. Returns `T::one()` for an empty slice.
228///
229/// # Examples
230///
231/// ```
232/// use mathverse_core::ops::product;
233///
234/// assert_eq!(product(&[1, 2, 3, 4]), 24);
235/// assert_eq!(product::<f64>(&[]), 1.0);
236/// ```
237#[must_use]
238pub fn product<T: Num>(xs: &[T]) -> T {
239    xs.iter().copied().fold(T::one(), |acc, x| acc * x)
240}
241
242/// Convert degrees to radians.
243///
244/// # Examples
245///
246/// ```
247/// use mathverse_core::ops::deg_to_rad;
248///
249/// let rad = deg_to_rad(180.0_f64);
250/// assert!((rad - core::f64::consts::PI).abs() < 1e-12);
251/// ```
252#[must_use]
253#[inline]
254pub fn deg_to_rad<T: Real>(d: T) -> T {
255    d * T::from_f64(crate::constants::DEG_TO_RAD)
256}
257
258/// Convert radians to degrees.
259///
260/// # Examples
261///
262/// ```
263/// use mathverse_core::ops::rad_to_deg;
264///
265/// let deg = rad_to_deg(core::f64::consts::PI);
266/// assert!((deg - 180.0).abs() < 1e-12);
267/// ```
268#[must_use]
269#[inline]
270pub fn rad_to_deg<T: Real>(r: T) -> T {
271    r * T::from_f64(crate::constants::RAD_TO_DEG)
272}
273
274/// Convert gradians to degrees (1 gradian = 0.9 degrees).
275///
276/// # Examples
277///
278/// ```
279/// use mathverse_core::ops::grad_to_deg;
280///
281/// assert_eq!(grad_to_deg(100.0), 90.0);
282/// ```
283#[must_use]
284#[inline]
285pub fn grad_to_deg<T: Real>(g: T) -> T {
286    g * T::from_f64(GRAD_TO_DEG)
287}
288
289/// Convert degrees to gradians.
290///
291/// # Examples
292///
293/// ```
294/// use mathverse_core::ops::deg_to_grad;
295///
296/// assert_eq!(deg_to_grad(90.0), 100.0);
297/// ```
298#[must_use]
299#[inline]
300pub fn deg_to_grad<T: Real>(d: T) -> T {
301    d * T::from_f64(DEG_TO_GRAD)
302}
303
304/// Convert gradians to radians.
305///
306/// # Examples
307///
308/// ```
309/// use mathverse_core::ops::grad_to_rad;
310///
311/// let rad = grad_to_rad(200.0);
312/// assert!((rad - core::f64::consts::PI).abs() < 1e-15);
313/// ```
314#[must_use]
315#[inline]
316pub fn grad_to_rad<T: Real>(g: T) -> T {
317    g * T::from_f64(GRAD_TO_RAD)
318}
319
320/// Convert radians to gradians.
321///
322/// # Examples
323///
324/// ```
325/// use mathverse_core::ops::rad_to_grad;
326///
327/// let grad = rad_to_grad(core::f64::consts::PI);
328/// assert!((grad - 200.0).abs() < 1e-10);
329/// ```
330#[must_use]
331#[inline]
332pub fn rad_to_grad<T: Real>(r: T) -> T {
333    r * T::from_f64(200.0 / core::f64::consts::PI)
334}
335
336/// Copy the sign of `b` to the magnitude of `a`.
337///
338/// # Examples
339///
340/// ```
341/// use mathverse_core::ops::copysign;
342///
343/// assert_eq!(copysign(3.0, -1.0), -3.0);
344/// assert_eq!(copysign(3.0, 1.0), 3.0);
345/// ```
346#[must_use]
347#[inline]
348pub fn copysign<T: Real>(a: T, b: T) -> T {
349    if b.is_negative() {
350        a.abs().neg()
351    } else {
352        a.abs()
353    }
354}
355
356/// Saturating subtraction: `max(0, a - b)`.
357///
358/// # Examples
359///
360/// ```
361/// use mathverse_core::ops::abs_sub;
362///
363/// assert_eq!(abs_sub(5.0, 3.0), 2.0);
364/// assert_eq!(abs_sub(3.0, 5.0), 0.0);
365/// ```
366#[must_use]
367#[inline]
368pub fn abs_sub<T: Real>(a: T, b: T) -> T {
369    (a - b).max(T::zero())
370}
371
372/// Map a value from one range to another.
373///
374/// `map_range(x, 0, 10, 0, 100)` maps 5 to 50.
375///
376/// # Examples
377///
378/// ```
379/// use mathverse_core::ops::map_range;
380///
381/// assert_eq!(map_range(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
382/// assert_eq!(map_range(0.0, 0.0, 10.0, 0.0, 100.0), 0.0);
383/// ```
384#[must_use]
385pub fn map_range<T: Real>(x: T, in_lo: T, in_hi: T, out_lo: T, out_hi: T) -> T {
386    let t = lerp_inv(x, in_lo, in_hi);
387    lerp(out_lo, out_hi, t)
388}
389
390/// Wrap `x` into the half-open interval `[lo, hi)`.
391///
392/// # Examples
393///
394/// ```
395/// use mathverse_core::ops::wrap;
396///
397/// assert_eq!(wrap(15.0, 0.0, 10.0), 5.0);
398/// assert_eq!(wrap(-3.0, 0.0, 10.0), 7.0);
399/// ```
400#[must_use]
401pub fn wrap<T: Real + Rem<Output = T>>(x: T, lo: T, hi: T) -> T {
402    let range = hi - lo;
403    if range == T::zero() {
404        return lo;
405    }
406    let v = x - lo;
407    let r = v % range;
408    if r < T::zero() {
409        r + hi
410    } else {
411        r + lo
412    }
413}
414
415/// Ping-pong: triangle wave between 0 and `length`.
416///
417/// # Examples
418///
419/// ```
420/// use mathverse_core::ops::ping_pong;
421///
422/// assert_eq!(ping_pong(15.0, 10.0), 5.0);
423/// assert_eq!(ping_pong(25.0, 10.0), 5.0);
424/// ```
425#[must_use]
426pub fn ping_pong<T: Real>(x: T, length: T) -> T {
427    let t = wrap(x, T::zero(), length);
428    length - (length - t).abs()
429}
430
431/// Repeat `x` into `[0, length)`.
432///
433/// # Examples
434///
435/// ```
436/// use mathverse_core::ops::repeat;
437///
438/// assert_eq!(repeat(15.0, 10.0), 5.0);
439/// assert_eq!(repeat(3.0, 10.0), 3.0);
440/// ```
441#[must_use]
442#[inline]
443pub fn repeat<T: Real>(x: T, length: T) -> T {
444    wrap(x, T::zero(), length)
445}
446
447/// Normalize an angle in radians to `[-pi, pi]`.
448///
449/// # Examples
450///
451/// ```
452/// use mathverse_core::ops::wrap_angle;
453///
454/// let a = wrap_angle(core::f64::consts::PI + 0.1);
455/// assert!((a - (-core::f64::consts::PI + 0.1)).abs() < 1e-15);
456/// ```
457#[must_use]
458pub fn wrap_angle<T: Real + Rem<Output = T>>(x: T) -> T {
459    let pi = T::from_f64(core::f64::consts::PI);
460    let two_pi = T::from_f64(core::f64::consts::TAU);
461    let v = x % two_pi;
462    if v > pi {
463        v - two_pi
464    } else if v < -pi {
465        v + two_pi
466    } else {
467        v
468    }
469}
470
471/// Wrap angle in radians to `[0, 2pi)`.
472///
473/// # Examples
474///
475/// ```
476/// use mathverse_core::ops::wrap_angle_positive;
477///
478/// let a = wrap_angle_positive(-0.1);
479/// assert!((a - (2.0 * core::f64::consts::PI - 0.1)).abs() < 1e-15);
480/// ```
481#[must_use]
482pub fn wrap_angle_positive<T: Real + Rem<Output = T>>(x: T) -> T {
483    let two_pi = T::from_f64(core::f64::consts::TAU);
484    let v = x % two_pi;
485    if v < T::zero() {
486        v + two_pi
487    } else {
488        v
489    }
490}
491
492/// Distance between two values: `|a - b|`.
493///
494/// # Examples
495///
496/// ```
497/// use mathverse_core::ops::distance;
498///
499/// assert_eq!(distance(3.0, 7.0), 4.0);
500/// assert_eq!(distance(5.0, 5.0), 0.0);
501/// ```
502#[must_use]
503#[inline]
504pub fn distance<T: Real>(a: T, b: T) -> T {
505    (a - b).abs()
506}
507
508/// Normalize a vector (represented as a slice) to unit length.
509///
510/// Returns a zero vector if the norm is zero.
511///
512/// # Examples
513///
514/// ```
515/// use mathverse_core::ops::normalize;
516///
517/// let v = normalize(&[3.0_f64, 4.0]);
518/// assert!((v[0] - 0.6).abs() < 1e-12);
519/// assert!((v[1] - 0.8).abs() < 1e-12);
520/// ```
521pub fn normalize<T: Real>(xs: &[T]) -> Vec<T> {
522    let norm_sq: T = xs.iter().copied().fold(T::zero(), |acc, x| acc + x * x);
523    let norm = norm_sq.sqrt();
524    if norm == T::zero() {
525        return vec![T::zero(); xs.len()];
526    }
527    xs.iter().map(|&x| x / norm).collect()
528}
529
530/// Sign as integer: -1, 0, or 1.
531///
532/// # Examples
533///
534/// ```
535/// use mathverse_core::ops::sign;
536///
537/// assert_eq!(sign(5.0), 1);
538/// assert_eq!(sign(-5.0), -1);
539/// assert_eq!(sign(0.0), 0);
540/// ```
541#[must_use]
542#[inline]
543pub fn sign<T: Real>(x: T) -> i32 {
544    if x > T::zero() {
545        1
546    } else if x < T::zero() {
547        -1
548    } else {
549        0
550    }
551}
552
553/// Truncate toward zero.
554///
555/// # Examples
556///
557/// ```
558/// use mathverse_core::ops::trunc;
559///
560/// assert_eq!(trunc(2.7), 2.0);
561/// assert_eq!(trunc(-2.7), -2.0);
562/// ```
563#[must_use]
564#[inline]
565pub fn trunc<T: Real>(x: T) -> T {
566    if x >= T::zero() {
567        x.floor()
568    } else {
569        x.ceil()
570    }
571}
572
573/// Fractional part toward zero: `x - trunc(x)`.
574///
575/// # Examples
576///
577/// ```
578/// use mathverse_core::ops::frac;
579///
580/// assert!((frac(2.7_f64) - 0.7).abs() < 1e-12);
581/// assert!((frac(-2.7_f64) - (-0.7)).abs() < 1e-12);
582/// ```
583#[must_use]
584#[inline]
585pub fn frac<T: Real>(x: T) -> T {
586    x - trunc(x)
587}
588
589/// Heaviside step function: `0` if `x < edge`, `1` if `x >= edge`.
590///
591/// # Examples
592///
593/// ```
594/// use mathverse_core::ops::step;
595///
596/// assert_eq!(step(0.0, -1.0), 0.0);
597/// assert_eq!(step(0.0, 0.0), 1.0);
598/// assert_eq!(step(0.0, 1.0), 1.0);
599/// ```
600#[must_use]
601#[inline]
602pub fn step<T: Real>(edge: T, x: T) -> T {
603    if x < edge {
604        T::zero()
605    } else {
606        T::one()
607    }
608}
609
610/// Clamp `x` into `[0, 1]`.
611///
612/// # Examples
613///
614/// ```
615/// use mathverse_core::ops::clamp01;
616///
617/// assert_eq!(clamp01(0.5), 0.5);
618/// assert_eq!(clamp01(-1.0), 0.0);
619/// assert_eq!(clamp01(2.0), 1.0);
620/// ```
621#[must_use]
622#[inline]
623pub fn clamp01<T: Real>(x: T) -> T {
624    clamp(x, T::zero(), T::one())
625}
626
627/// Clamp `x` into `[-1, 1]`.
628///
629/// # Examples
630///
631/// ```
632/// use mathverse_core::ops::clamp11;
633///
634/// assert_eq!(clamp11(0.5), 0.5);
635/// assert_eq!(clamp11(-2.0), -1.0);
636/// assert_eq!(clamp11(3.0), 1.0);
637/// ```
638#[must_use]
639#[inline]
640pub fn clamp11<T: Real>(x: T) -> T {
641    clamp(x, T::zero() - T::one(), T::one())
642}
643
644/// Snap `x` to the nearest multiple of `step`.
645///
646/// # Examples
647///
648/// ```
649/// use mathverse_core::ops::snap;
650///
651/// assert_eq!(snap(7.0, 5.0), 5.0);
652/// assert_eq!(snap(8.0, 5.0), 10.0);
653/// assert_eq!(snap(0.0, 3.0), 0.0);
654/// ```
655#[must_use]
656pub fn snap<T: Real>(x: T, step_size: T) -> T {
657    if step_size == T::zero() {
658        return x;
659    }
660    (x / step_size).round() * step_size
661}
662
663/// Overflow-safe midpoint: `(a + b) / 2` without intermediate overflow.
664///
665/// # Examples
666///
667/// ```
668/// use mathverse_core::ops::midpoint;
669///
670/// assert_eq!(midpoint(0.0, 10.0), 5.0);
671/// assert_eq!(midpoint(-3.0, 3.0), 0.0);
672/// assert_eq!(midpoint(1e200, 1e200), 1e200);
673/// ```
674#[must_use]
675#[inline]
676pub fn midpoint<T: Real>(a: T, b: T) -> T {
677    let two = T::one() + T::one();
678    a + (b - a) / two
679}
680
681/// Shortest-path angular interpolation between `a` and `b` in radians.
682///
683/// `t = 0` returns `a`, `t = 1` returns `b`. Takes the shorter arc.
684///
685/// # Examples
686///
687/// ```
688/// use mathverse_core::ops::lerp_angle;
689///
690/// let result = lerp_angle(0.0, core::f64::consts::PI, 0.5);
691/// assert!((result - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
692/// ```
693#[must_use]
694pub fn lerp_angle<T: Real + Rem<Output = T>>(a: T, b: T, t: T) -> T {
695    let pi = T::from_f64(core::f64::consts::PI);
696    let two_pi = T::from_f64(core::f64::consts::TAU);
697    let mut diff = (b - a) % two_pi;
698    if diff > pi {
699        diff = diff - two_pi;
700    } else if diff < -pi {
701        diff = diff + two_pi;
702    }
703    a + diff * t
704}
705
706/// Inverse linear interpolation: find `t` such that `lerp(a, b, t) == x`.
707///
708/// Alias for [`lerp_inv`].
709///
710/// # Examples
711///
712/// ```
713/// use mathverse_core::ops::inv_lerp;
714///
715/// assert_eq!(inv_lerp(5.0, 0.0, 10.0), 0.5);
716/// assert_eq!(inv_lerp(0.0, 0.0, 10.0), 0.0);
717/// ```
718#[must_use]
719#[inline]
720pub fn inv_lerp<T: Real>(x: T, a: T, b: T) -> T {
721    lerp_inv(x, a, b)
722}
723
724/// Remap `x` from range `[in_lo, in_hi]` to `[out_lo, out_hi]`.
725///
726/// Alias for [`map_range`] with clearer naming.
727///
728/// # Examples
729///
730/// ```
731/// use mathverse_core::ops::remap;
732///
733/// assert_eq!(remap(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
734/// assert_eq!(remap(0.0, 0.0, 10.0, 0.0, 100.0), 0.0);
735/// ```
736#[must_use]
737#[inline]
738pub fn remap<T: Real>(x: T, in_lo: T, in_hi: T, out_lo: T, out_hi: T) -> T {
739    map_range(x, in_lo, in_hi, out_lo, out_hi)
740}
741
742/// Returns `true` if `x` is within `[lo, hi]` (inclusive).
743///
744/// # Examples
745///
746/// ```
747/// use mathverse_core::ops::is_between;
748///
749/// assert!(is_between(5.0, 0.0, 10.0));
750/// assert!(!is_between(15.0, 0.0, 10.0));
751/// assert!(is_between(0.0, 0.0, 10.0));
752/// assert!(is_between(10.0, 0.0, 10.0));
753/// ```
754#[must_use]
755#[inline]
756pub fn is_between<T: Copy + PartialOrd>(x: T, lo: T, hi: T) -> bool {
757    x >= lo && x <= hi
758}
759
760/// Linear interpolation with `t` clamped to `[0, 1]`.
761///
762/// # Examples
763///
764/// ```
765/// use mathverse_core::ops::lerp_clamped;
766///
767/// assert_eq!(lerp_clamped(0.0, 10.0, 0.25), 2.5);
768/// assert_eq!(lerp_clamped(0.0, 10.0, -1.0), 0.0);
769/// assert_eq!(lerp_clamped(0.0, 10.0, 2.0), 10.0);
770/// ```
771#[must_use]
772#[inline]
773pub fn lerp_clamped<T: Real>(a: T, b: T, t: T) -> T {
774    lerp(a, b, clamp01(t))
775}
776
777/// Remap `x` from `[in_lo, in_hi]` to `[out_lo, out_hi]` with output clamped.
778///
779/// # Examples
780///
781/// ```
782/// use mathverse_core::ops::remap_clamped;
783///
784/// assert_eq!(remap_clamped(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
785/// assert_eq!(remap_clamped(-5.0, 0.0, 10.0, 0.0, 100.0), 0.0);
786/// assert_eq!(remap_clamped(15.0, 0.0, 10.0, 0.0, 100.0), 100.0);
787/// ```
788#[must_use]
789#[inline]
790pub fn remap_clamped<T: Real>(x: T, in_lo: T, in_hi: T, out_lo: T, out_hi: T) -> T {
791    lerp_clamped(out_lo, out_hi, lerp_inv(x, in_lo, in_hi))
792}
793
794/// Bilinear interpolation between four corner values.
795///
796/// `v00` at `(0,0)`, `v01` at `(0,1)`, `v10` at `(1,0)`, `v11` at `(1,1)`.
797/// `x` and `y` are assumed within `[0, 1]`.
798///
799/// # Examples
800///
801/// ```
802/// use mathverse_core::ops::bilinear;
803///
804/// assert_eq!(bilinear(0.0, 0.0, 0.0, 0.0, 0.0, 10.0), 0.0);
805/// assert_eq!(bilinear(1.0, 1.0, 0.0, 0.0, 0.0, 10.0), 10.0);
806/// assert_eq!(bilinear(0.5, 0.5, 0.0, 10.0, 10.0, 0.0), 5.0);
807/// ```
808#[must_use]
809pub fn bilinear<T: Real>(x: T, y: T, v00: T, v01: T, v10: T, v11: T) -> T {
810    let top = lerp(v00, v10, x);
811    let bottom = lerp(v01, v11, x);
812    lerp(top, bottom, y)
813}
814
815/// Check if a slice is sorted in non-decreasing order.
816///
817/// # Examples
818///
819/// ```
820/// use mathverse_core::ops::is_sorted;
821///
822/// assert!(is_sorted(&[1, 2, 3, 4]));
823/// assert!(is_sorted(&[1, 1, 2, 3]));
824/// assert!(!is_sorted(&[1, 3, 2, 4]));
825/// assert!(is_sorted::<i32>(&[]));
826/// assert!(is_sorted(&[42]));
827/// ```
828#[must_use]
829pub fn is_sorted<T: PartialOrd>(xs: &[T]) -> bool {
830    xs.windows(2).all(|w| w[0] <= w[1])
831}
832
833/// Minimum value in a slice. Returns `None` for an empty slice.
834///
835/// # Examples
836///
837/// ```
838/// use mathverse_core::ops::min_value;
839///
840/// assert_eq!(min_value(&[3, 1, 4, 1, 5]), Some(&1));
841/// assert_eq!(min_value::<i32>(&[]), None);
842/// ```
843#[must_use]
844pub fn min_value<T: PartialOrd>(xs: &[T]) -> Option<&T> {
845    xs.iter().min_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
846}
847
848/// Maximum value in a slice. Returns `None` for an empty slice.
849///
850/// # Examples
851///
852/// ```
853/// use mathverse_core::ops::max_value;
854///
855/// assert_eq!(max_value(&[3, 1, 4, 1, 5]), Some(&5));
856/// assert_eq!(max_value::<i32>(&[]), None);
857/// ```
858#[must_use]
859pub fn max_value<T: PartialOrd>(xs: &[T]) -> Option<&T> {
860    xs.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
861}
862
863/// Mean (average) of a slice. Returns `T::zero()` for an empty slice.
864///
865/// # Examples
866///
867/// ```
868/// use mathverse_core::ops::mean;
869///
870/// assert_eq!(mean(&[1.0, 2.0, 3.0, 4.0]), 2.5);
871/// assert_eq!(mean::<f64>(&[]), 0.0);
872/// ```
873#[must_use]
874pub fn mean<T: Real>(xs: &[T]) -> T {
875    if xs.is_empty() {
876        return T::zero();
877    }
878    let sum: T = xs.iter().copied().fold(T::zero(), |acc, x| acc + x);
879    sum / T::from_f64(f64::from(xs.len() as u32))
880}
881
882/// Cumulative sum of a slice. Returns an empty vector for an empty input.
883///
884/// # Examples
885///
886/// ```
887/// use mathverse_core::ops::cumsum;
888///
889/// assert_eq!(cumsum(&[1, 2, 3, 4]), vec![1, 3, 6, 10]);
890/// assert_eq!(cumsum::<i32>(&[]), vec![]);
891/// ```
892#[must_use]
893pub fn cumsum<T: Num + Copy + core::ops::Add<Output = T>>(xs: &[T]) -> Vec<T> {
894    let mut result = Vec::with_capacity(xs.len());
895    let mut acc = T::zero();
896    for &x in xs {
897        acc = acc + x;
898        result.push(acc);
899    }
900    result
901}
902
903/// Cumulative product of a slice. Returns an empty vector for an empty input.
904///
905/// # Examples
906///
907/// ```
908/// use mathverse_core::ops::cumprod;
909///
910/// assert_eq!(cumprod(&[1, 2, 3, 4]), vec![1, 2, 6, 24]);
911/// assert_eq!(cumprod::<i32>(&[]), vec![]);
912/// ```
913#[must_use]
914pub fn cumprod<T: Num + Copy + core::ops::Mul<Output = T>>(xs: &[T]) -> Vec<T> {
915    let mut result = Vec::with_capacity(xs.len());
916    let mut acc = T::one();
917    for &x in xs {
918        acc = acc * x;
919        result.push(acc);
920    }
921    result
922}
923
924/// Dot product of two slices. Returns `T::zero()` if either is empty.
925/// Panics if lengths differ.
926///
927/// # Examples
928///
929/// ```
930/// use mathverse_core::ops::dot_product;
931///
932/// assert_eq!(dot_product(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]), 32.0);
933/// assert_eq!(dot_product(&[2, 3], &[4, 5]), 23);
934/// ```
935#[must_use]
936pub fn dot_product<T: Num + Copy + core::ops::Add<Output = T> + core::ops::Mul<Output = T>>(
937    a: &[T],
938    b: &[T],
939) -> T {
940    a.iter().zip(b.iter()).map(|(&x, &y)| x * y).fold(T::zero(), |acc, x| acc + x)
941}
942
943/// Parameterized smoothstep: C1 interpolation between `edge0` and `edge1`.
944///
945/// Returns `0` when `x <= edge0`, `1` when `x >= edge1`, and a smooth
946/// Hermite interpolation between them otherwise.
947/// Formula: `t = clamp((x - edge0) / (edge1 - edge0), 0, 1)`,
948/// result: `t^2 * (3 - 2t)`.
949///
950/// # Examples
951///
952/// ```
953/// use mathverse_core::ops::smoothstep_between;
954///
955/// assert_eq!(smoothstep_between(0.0, 1.0, 0.0), 0.0);
956/// assert_eq!(smoothstep_between(0.0, 1.0, 0.5), 0.5);
957/// assert_eq!(smoothstep_between(0.0, 1.0, 1.0), 1.0);
958/// assert_eq!(smoothstep_between(2.0, 5.0, 3.5), 0.5);
959/// assert_eq!(smoothstep_between(0.0, 1.0, -1.0), 0.0);
960/// assert_eq!(smoothstep_between(0.0, 1.0, 2.0), 1.0);
961/// ```
962#[must_use]
963pub fn smoothstep_between<T: Real>(edge0: T, edge1: T, x: T) -> T {
964    let t = clamp((x - edge0) / (edge1 - edge0), T::zero(), T::one());
965    t * t * (T::from_f64(3.0) - T::from_f64(2.0) * t)
966}
967
968#[cfg(test)]
969#[allow(clippy::suboptimal_flops)]
970mod tests {
971    use super::*;
972
973    #[test]
974    fn basic_ops() {
975        assert_eq!(clamp(5.0, 0.0, 3.0), 3.0);
976        assert_eq!(lerp(0.0, 10.0, 0.25), 2.5);
977        assert_eq!(smoothstep(0.5), 0.5);
978        assert_eq!(fract(2.75), 0.75);
979        assert_eq!(fract(-0.25), 0.75);
980        assert_eq!(nth_root(27.0, 3), 3.0);
981    }
982
983    #[test]
984    fn hypot_no_overflow() {
985        let big = 1e150f64;
986        assert_eq!(hypot2(big, big), big * 2.0f64.sqrt());
987        assert_eq!(hypot2(0.0, 0.0), 0.0);
988        assert_eq!(hypot3(3.0, 4.0, 12.0), 13.0);
989    }
990
991    #[test]
992    fn aggregates() {
993        assert_eq!(sum(&[1, 2, 3, 4]), 10);
994        assert_eq!(sum::<i32>(&[]), 0);
995        assert_eq!(product(&[1, 2, 3, 4]), 24);
996        assert_eq!(product::<f64>(&[]), 1.0);
997    }
998
999    #[test]
1000    fn angle_conversions() {
1001        assert!((rad_to_deg(deg_to_rad(45.0f64)) - 45.0).abs() < 1e-12);
1002        assert_eq!(grad_to_deg(100.0), 90.0);
1003        assert_eq!(deg_to_grad(90.0), 100.0);
1004        assert!((rad_to_grad(3.14159265358979f64) - 200.0f64).abs() < 1e-10);
1005        assert!((grad_to_rad(200.0) - core::f64::consts::PI).abs() < 1e-15);
1006    }
1007
1008    #[test]
1009    fn new_ops() {
1010        assert_eq!(cbrt(27.0), 3.0);
1011        assert_eq!(copysign(3.0, -1.0), -3.0);
1012        assert_eq!(abs_sub(5.0, 3.0), 2.0);
1013        assert_eq!(abs_sub(3.0, 5.0), 0.0);
1014        assert_eq!(map_range(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
1015        assert_eq!(lerp_inv(5.0, 0.0, 10.0), 0.5);
1016        assert_eq!(smootherstep(0.5), 0.5);
1017        assert_eq!(sign(5.0), 1);
1018        assert_eq!(sign(-5.0), -1);
1019        assert_eq!(sign(0.0), 0);
1020        assert_eq!(trunc(2.7), 2.0);
1021        assert_eq!(trunc(-2.7), -2.0);
1022        assert!((frac(2.7f64) - 0.7f64).abs() < 1e-12);
1023        assert!((frac(-2.7f64) - (-0.7f64)).abs() < 1e-12);
1024        assert_eq!(distance(3.0, 7.0), 4.0);
1025    }
1026
1027    #[test]
1028    fn wrap_and_angle() {
1029        assert_eq!(wrap(15.0, 0.0, 10.0), 5.0);
1030        assert_eq!(wrap(-3.0, 0.0, 10.0), 7.0);
1031        assert_eq!(repeat(15.0, 10.0), 5.0);
1032        assert_eq!(ping_pong(15.0, 10.0), 5.0);
1033        assert_eq!(ping_pong(25.0, 10.0), 5.0);
1034        assert!((wrap_angle(core::f64::consts::PI + 0.1) - (-core::f64::consts::PI + 0.1)).abs() < 1e-15);
1035        assert!((wrap_angle_positive(-0.1) - (2.0 * core::f64::consts::PI - 0.1)).abs() < 1e-15);
1036    }
1037
1038    #[test]
1039    fn normalize_vec() {
1040        let v = normalize(&[3.0, 4.0]);
1041        assert!((v[0] - 0.6f64).abs() < 1e-12);
1042        assert!((v[1] - 0.8f64).abs() < 1e-12);
1043        let zero = normalize(&[0.0, 0.0]);
1044        assert_eq!(zero, vec![0.0, 0.0]);
1045    }
1046
1047    #[test]
1048    fn new_ops_extra() {
1049        assert_eq!(step(0.0, -1.0), 0.0);
1050        assert_eq!(step(0.0, 0.0), 1.0);
1051        assert_eq!(step(0.0, 1.0), 1.0);
1052        assert_eq!(clamp01(0.5), 0.5);
1053        assert_eq!(clamp01(-1.0), 0.0);
1054        assert_eq!(clamp01(2.0), 1.0);
1055        assert_eq!(clamp11(0.5), 0.5);
1056        assert_eq!(clamp11(-2.0), -1.0);
1057        assert_eq!(clamp11(3.0), 1.0);
1058        assert_eq!(snap(7.0, 5.0), 5.0);
1059        assert_eq!(snap(8.0, 5.0), 10.0);
1060        assert_eq!(snap(0.0, 3.0), 0.0);
1061        assert_eq!(midpoint(0.0, 10.0), 5.0);
1062        assert_eq!(midpoint(-3.0, 3.0), 0.0);
1063        assert_eq!(midpoint(1e200, 1e200), 1e200);
1064        let result = lerp_angle(0.0, core::f64::consts::PI, 0.5);
1065        assert!((result - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
1066        assert_eq!(inv_lerp(5.0, 0.0, 10.0), 0.5);
1067        assert_eq!(remap(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
1068    }
1069
1070    #[test]
1071    fn smoothstep_between_tests() {
1072        assert_eq!(smoothstep_between(0.0, 1.0, 0.0), 0.0);
1073        assert_eq!(smoothstep_between(0.0, 1.0, 0.5), 0.5);
1074        assert_eq!(smoothstep_between(0.0, 1.0, 1.0), 1.0);
1075        assert_eq!(smoothstep_between(2.0, 5.0, 3.5), 0.5);
1076        assert_eq!(smoothstep_between(0.0, 1.0, -1.0), 0.0);
1077        assert_eq!(smoothstep_between(0.0, 1.0, 2.0), 1.0);
1078    }
1079
1080    #[test]
1081    fn is_between_tests() {
1082        assert!(is_between(5.0, 0.0, 10.0));
1083        assert!(!is_between(15.0, 0.0, 10.0));
1084        assert!(!is_between(-1.0, 0.0, 10.0));
1085        assert!(is_between(0.0, 0.0, 10.0));
1086        assert!(is_between(10.0, 0.0, 10.0));
1087    }
1088
1089    #[test]
1090    fn lerp_clamped_tests() {
1091        assert_eq!(lerp_clamped(0.0, 10.0, 0.25), 2.5);
1092        assert_eq!(lerp_clamped(0.0, 10.0, -1.0), 0.0);
1093        assert_eq!(lerp_clamped(0.0, 10.0, 2.0), 10.0);
1094    }
1095
1096    #[test]
1097    fn remap_clamped_tests() {
1098        assert_eq!(remap_clamped(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
1099        assert_eq!(remap_clamped(-5.0, 0.0, 10.0, 0.0, 100.0), 0.0);
1100        assert_eq!(remap_clamped(15.0, 0.0, 10.0, 0.0, 100.0), 100.0);
1101    }
1102
1103    #[test]
1104    fn bilinear_tests() {
1105        assert_eq!(bilinear(0.0, 0.0, 0.0, 0.0, 0.0, 10.0), 0.0);
1106        assert_eq!(bilinear(1.0, 1.0, 0.0, 0.0, 0.0, 10.0), 10.0);
1107        assert_eq!(bilinear(0.5, 0.5, 0.0, 10.0, 10.0, 0.0), 5.0);
1108        assert_eq!(bilinear(0.5, 0.0, 0.0, 10.0, 20.0, 30.0), 10.0);
1109    }
1110
1111    #[test]
1112    fn is_sorted_tests() {
1113        assert!(is_sorted(&[1, 2, 3, 4]));
1114        assert!(is_sorted(&[1, 1, 2, 3]));
1115        assert!(!is_sorted(&[1, 3, 2, 4]));
1116        assert!(is_sorted::<i32>(&[]));
1117        assert!(is_sorted(&[42]));
1118    }
1119
1120    #[test]
1121    fn min_max_value_tests() {
1122        assert_eq!(min_value(&[3, 1, 4, 1, 5]), Some(&1));
1123        assert_eq!(min_value::<i32>(&[]), None);
1124        assert_eq!(max_value(&[3, 1, 4, 1, 5]), Some(&5));
1125        assert_eq!(max_value::<i32>(&[]), None);
1126    }
1127
1128    #[test]
1129    fn mean_tests() {
1130        assert_eq!(mean(&[1.0, 2.0, 3.0, 4.0]), 2.5);
1131        assert_eq!(mean::<f64>(&[]), 0.0);
1132        assert_eq!(mean(&[5.0]), 5.0);
1133    }
1134
1135    #[test]
1136    fn cumsum_tests() {
1137        assert_eq!(cumsum(&[1, 2, 3, 4]), vec![1, 3, 6, 10]);
1138        assert_eq!(cumsum::<i32>(&[]), vec![]);
1139        assert_eq!(cumsum(&[5]), vec![5]);
1140    }
1141
1142    #[test]
1143    fn cumprod_tests() {
1144        assert_eq!(cumprod(&[1, 2, 3, 4]), vec![1, 2, 6, 24]);
1145        assert_eq!(cumprod::<i32>(&[]), vec![]);
1146        assert_eq!(cumprod(&[5]), vec![5]);
1147    }
1148
1149    #[test]
1150    fn dot_product_tests() {
1151        assert_eq!(dot_product(&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]), 32.0);
1152        assert_eq!(dot_product(&[2, 3], &[4, 5]), 23);
1153        assert_eq!(dot_product::<f64>(&[], &[]), 0.0);
1154    }
1155}