Skip to main content

ferray_ufunc/ops/
floatintrinsic.rs

1// ferray-ufunc: Floating-point intrinsic functions
2//
3// isnan, isinf, isfinite, isneginf, isposinf, nan_to_num, nextafter,
4// spacing, ldexp, frexp, signbit, copysign, float_power, fmax, fmin,
5// maximum, minimum, clip
6
7use ferray_core::Array;
8use ferray_core::dimension::Dimension;
9use ferray_core::dtype::Element;
10use ferray_core::error::{FerrayError, FerrayResult};
11use num_traits::Float;
12
13use crate::helpers::{binary_elementwise_op, binary_mixed_op, unary_float_op, unary_map_op};
14
15// ---------------------------------------------------------------------------
16// Classification
17// ---------------------------------------------------------------------------
18
19/// Elementwise test for NaN.
20pub fn isnan<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
21where
22    T: Element + Float,
23    D: Dimension,
24{
25    unary_map_op(input, num_traits::Float::is_nan)
26}
27
28/// Elementwise test for infinity (positive or negative).
29pub fn isinf<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
30where
31    T: Element + Float,
32    D: Dimension,
33{
34    unary_map_op(input, num_traits::Float::is_infinite)
35}
36
37/// Elementwise test for finiteness.
38pub fn isfinite<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
39where
40    T: Element + Float,
41    D: Dimension,
42{
43    unary_map_op(input, num_traits::Float::is_finite)
44}
45
46/// Elementwise test for negative infinity.
47pub fn isneginf<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
48where
49    T: Element + Float,
50    D: Dimension,
51{
52    unary_map_op(input, |x| x.is_infinite() && x < <T as Element>::zero())
53}
54
55/// Elementwise test for positive infinity.
56pub fn isposinf<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
57where
58    T: Element + Float,
59    D: Dimension,
60{
61    unary_map_op(input, |x| x.is_infinite() && x > <T as Element>::zero())
62}
63
64/// Elementwise sign bit test.
65pub fn signbit<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
66where
67    T: Element + Float,
68    D: Dimension,
69{
70    unary_map_op(input, num_traits::Float::is_sign_negative)
71}
72
73// ---------------------------------------------------------------------------
74// Replacement
75// ---------------------------------------------------------------------------
76
77/// Replace NaN with zero, and infinity with large finite numbers.
78///
79/// `nan` replaces NaN (default 0), `posinf` replaces +inf, `neginf` replaces -inf.
80pub fn nan_to_num<T, D>(
81    input: &Array<T, D>,
82    nan: Option<T>,
83    posinf: Option<T>,
84    neginf: Option<T>,
85) -> FerrayResult<Array<T, D>>
86where
87    T: Element + Float,
88    D: Dimension,
89{
90    let nan_val = nan.unwrap_or_else(|| <T as Element>::zero());
91    let posinf_val = posinf.unwrap_or_else(T::max_value);
92    let neginf_val = neginf.unwrap_or_else(T::min_value);
93    unary_float_op(input, |x| {
94        if x.is_nan() {
95            nan_val
96        } else if x.is_infinite() {
97            if x > <T as Element>::zero() {
98                posinf_val
99            } else {
100                neginf_val
101            }
102        } else {
103            x
104        }
105    })
106}
107
108/// Clip (limit) values to [`a_min`, `a_max`] for float types.
109pub fn clip<T, D>(input: &Array<T, D>, a_min: T, a_max: T) -> FerrayResult<Array<T, D>>
110where
111    T: Element + Float,
112    D: Dimension,
113{
114    if a_min > a_max {
115        return Err(FerrayError::invalid_value("clip: a_min must be <= a_max"));
116    }
117    unary_float_op(input, |x| {
118        if x < a_min {
119            a_min
120        } else if x > a_max {
121            a_max
122        } else {
123            x
124        }
125    })
126}
127
128/// Clip (limit) values to [`a_min`, `a_max`] for any ordered type, including integers.
129///
130/// This is a more general version of [`clip`] that works on integer arrays
131/// (i8, i16, i32, i64, u8, u16, u32, u64) as well as floats.
132///
133/// Equivalent to `numpy.clip`.
134pub fn clip_ord<T, D>(input: &Array<T, D>, a_min: T, a_max: T) -> FerrayResult<Array<T, D>>
135where
136    T: Element + PartialOrd + Copy,
137    D: Dimension,
138{
139    if a_min > a_max {
140        return Err(FerrayError::invalid_value(
141            "clip_ord: a_min must be <= a_max",
142        ));
143    }
144    let data: Vec<T> = input
145        .iter()
146        .map(|&x| {
147            if x < a_min {
148                a_min
149            } else if x > a_max {
150                a_max
151            } else {
152                x
153            }
154        })
155        .collect();
156    Array::from_vec(input.dim().clone(), data)
157}
158
159// ---------------------------------------------------------------------------
160// Float manipulation
161// ---------------------------------------------------------------------------
162
163/// IEEE 754 nextafter for f64: return the next representable value after `a` towards `b`.
164#[inline]
165fn nextafter_f64(a: f64, b: f64) -> f64 {
166    if a.is_nan() || b.is_nan() {
167        return f64::NAN;
168    }
169    if a == b {
170        return b;
171    }
172    if a == 0.0 {
173        // Return smallest subnormal with the sign of (b - a)
174        return if b > 0.0 {
175            f64::from_bits(1)
176        } else {
177            f64::from_bits(1u64 | (1u64 << 63))
178        };
179    }
180    let bits = a.to_bits();
181    let new_bits = if (a < b) == (a > 0.0) {
182        // Moving away from zero: increment magnitude
183        bits + 1
184    } else {
185        // Moving toward zero: decrement magnitude
186        bits - 1
187    };
188    f64::from_bits(new_bits)
189}
190
191/// IEEE 754 nextafter for f32: return the next representable value after `a` towards `b`.
192#[inline]
193fn nextafter_f32(a: f32, b: f32) -> f32 {
194    if a.is_nan() || b.is_nan() {
195        return f32::NAN;
196    }
197    if a == b {
198        return b;
199    }
200    if a == 0.0 {
201        return if b > 0.0 {
202            f32::from_bits(1)
203        } else {
204            f32::from_bits(1u32 | (1u32 << 31))
205        };
206    }
207    let bits = a.to_bits();
208    let new_bits = if (a < b) == (a > 0.0) {
209        bits + 1
210    } else {
211        bits - 1
212    };
213    f32::from_bits(new_bits)
214}
215
216/// Return the next floating-point value after x1 towards x2, using IEEE 754 bit manipulation.
217pub fn nextafter<T, D>(x1: &Array<T, D>, x2: &Array<T, D>) -> FerrayResult<Array<T, D>>
218where
219    T: Element + Float,
220    D: Dimension,
221{
222    use std::any::TypeId;
223
224    if TypeId::of::<T>() == TypeId::of::<f64>() {
225        // SAFETY: T is f64, verified by TypeId check above.
226        let a_f64 = unsafe { &*std::ptr::from_ref::<Array<T, D>>(x1).cast::<Array<f64, D>>() };
227        let b_f64 = unsafe { &*std::ptr::from_ref::<Array<T, D>>(x2).cast::<Array<f64, D>>() };
228        let result = binary_elementwise_op(a_f64, b_f64, nextafter_f64)?;
229        Ok(unsafe { crate::helpers::reinterpret_array::<f64, T, D>(result) })
230    } else if TypeId::of::<T>() == TypeId::of::<f32>() {
231        // SAFETY: T is f32, verified by TypeId check above.
232        let a_f32 = unsafe { &*std::ptr::from_ref::<Array<T, D>>(x1).cast::<Array<f32, D>>() };
233        let b_f32 = unsafe { &*std::ptr::from_ref::<Array<T, D>>(x2).cast::<Array<f32, D>>() };
234        let result = binary_elementwise_op(a_f32, b_f32, nextafter_f32)?;
235        Ok(unsafe { crate::helpers::reinterpret_array::<f32, T, D>(result) })
236    } else {
237        // Fallback for other float types: use f64 round-trip
238        binary_elementwise_op(x1, x2, |a, b| {
239            let a64 = a.to_f64().unwrap();
240            let b64 = b.to_f64().unwrap();
241            T::from(nextafter_f64(a64, b64)).unwrap()
242        })
243    }
244}
245
246/// IEEE 754 spacing (ULP) for f64.
247#[inline]
248fn spacing_f64(x: f64) -> f64 {
249    if x.is_nan() || x.is_infinite() {
250        return f64::NAN;
251    }
252    let ax = x.abs();
253    if ax == 0.0 {
254        return f64::from_bits(1); // smallest positive subnormal
255    }
256    nextafter_f64(ax, f64::INFINITY) - ax
257}
258
259/// IEEE 754 spacing (ULP) for f32.
260#[inline]
261fn spacing_f32(x: f32) -> f32 {
262    if x.is_nan() || x.is_infinite() {
263        return f32::NAN;
264    }
265    let ax = x.abs();
266    if ax == 0.0 {
267        return f32::from_bits(1); // smallest positive subnormal
268    }
269    nextafter_f32(ax, f32::INFINITY) - ax
270}
271
272/// Return the spacing of values: the ULP (unit in the last place),
273/// computed via IEEE 754 bit manipulation.
274pub fn spacing<T, D>(input: &Array<T, D>) -> FerrayResult<Array<T, D>>
275where
276    T: Element + Float,
277    D: Dimension,
278{
279    use std::any::TypeId;
280
281    if TypeId::of::<T>() == TypeId::of::<f64>() {
282        let f64_input =
283            unsafe { &*std::ptr::from_ref::<Array<T, D>>(input).cast::<Array<f64, D>>() };
284        let result = unary_float_op(f64_input, spacing_f64)?;
285        Ok(unsafe { crate::helpers::reinterpret_array::<f64, T, D>(result) })
286    } else if TypeId::of::<T>() == TypeId::of::<f32>() {
287        let f32_input =
288            unsafe { &*std::ptr::from_ref::<Array<T, D>>(input).cast::<Array<f32, D>>() };
289        let result = unary_float_op(f32_input, spacing_f32)?;
290        Ok(unsafe { crate::helpers::reinterpret_array::<f32, T, D>(result) })
291    } else {
292        // Fallback for other float types: use f64 round-trip
293        unary_float_op(input, |x| {
294            let x64 = x.to_f64().unwrap();
295            T::from(spacing_f64(x64)).unwrap()
296        })
297    }
298}
299
300/// Multiply `x` by `2^n` (ldexp), with `NumPy` broadcasting.
301///
302/// `n` is provided as an integer array; broadcast-compatible with `x`.
303pub fn ldexp<T, D>(x: &Array<T, D>, n: &Array<i32, D>) -> FerrayResult<Array<T, D>>
304where
305    T: Element + Float,
306    D: Dimension,
307{
308    let two = <T as Element>::one() + <T as Element>::one();
309    binary_mixed_op(x, n, move |xi, ni| xi * two.powi(ni))
310}
311
312/// Decompose f64 into mantissa and exponent via IEEE 754 bit extraction.
313///
314/// Returns (mantissa, exponent) where mantissa is in [0.5, 1.0) and x = mantissa * 2^exponent.
315#[inline]
316fn frexp_f64(x: f64) -> (f64, i32) {
317    if x == 0.0 || x.is_nan() || x.is_infinite() {
318        return (x, 0);
319    }
320    let bits = x.to_bits();
321    let sign = bits & (1u64 << 63);
322    let mut exp = ((bits >> 52) & 0x7FF) as i32;
323    if exp == 0 {
324        // Subnormal: multiply by 2^64 to normalize, then adjust exponent
325        let nx = x * (1u64 << 63) as f64 * 2.0;
326        let nbits = nx.to_bits();
327        exp = ((nbits >> 52) & 0x7FF) as i32 - 64;
328        let frac = f64::from_bits((nbits & !(0x7FFu64 << 52)) | (0x3FEu64 << 52));
329        return (if sign != 0 { -frac.abs() } else { frac }, exp - 0x3FE);
330    }
331    // Set exponent to -1 (biased: 0x3FE) to place mantissa in [0.5, 1.0)
332    let frac = f64::from_bits((bits & !(0x7FFu64 << 52)) | (0x3FEu64 << 52));
333    // exponent = (biased_exp - 1023) + 1 = biased_exp - 0x3FE
334    (frac, exp - 0x3FE)
335}
336
337/// Decompose f32 into mantissa and exponent via IEEE 754 bit extraction.
338///
339/// Returns (mantissa, exponent) where mantissa is in [0.5, 1.0) and x = mantissa * 2^exponent.
340#[inline]
341fn frexp_f32(x: f32) -> (f32, i32) {
342    if x == 0.0 || x.is_nan() || x.is_infinite() {
343        return (x, 0);
344    }
345    let bits = x.to_bits();
346    let sign = bits & (1u32 << 31);
347    let mut exp = ((bits >> 23) & 0xFF) as i32;
348    if exp == 0 {
349        // Subnormal: multiply by 2^32 to normalize, then adjust exponent
350        let nx = x * (1u32 << 31) as f32 * 2.0;
351        let nbits = nx.to_bits();
352        exp = ((nbits >> 23) & 0xFF) as i32 - 32;
353        let frac = f32::from_bits((nbits & !(0xFFu32 << 23)) | (0x7Eu32 << 23));
354        return (if sign != 0 { -frac.abs() } else { frac }, exp - 0x7E);
355    }
356    let frac = f32::from_bits((bits & !(0xFFu32 << 23)) | (0x7Eu32 << 23));
357    (frac, exp - 0x7E)
358}
359
360/// Decompose into mantissa and exponent: x = m * 2^e.
361///
362/// Returns (`mantissa_array`, `exponent_array`) where mantissa is in [0.5, 1.0).
363/// This matches C's `frexp`: for x=4.0, returns (0.5, 3) since 0.5 * 2^3 = 4.
364///
365/// Uses IEEE 754 bit extraction for f64 and f32 (O(1) per element), with
366/// f64 round-trip fallback for other float types.
367pub fn frexp<T, D>(input: &Array<T, D>) -> FerrayResult<(Array<T, D>, Array<i32, D>)>
368where
369    T: Element + Float,
370    D: Dimension,
371{
372    use std::any::TypeId;
373
374    if TypeId::of::<T>() == TypeId::of::<f64>() {
375        // SAFETY: T is f64, verified by TypeId check above.
376        let f64_input =
377            unsafe { &*std::ptr::from_ref::<Array<T, D>>(input).cast::<Array<f64, D>>() };
378        let mut mantissas = Vec::with_capacity(f64_input.size());
379        let mut exponents = Vec::with_capacity(f64_input.size());
380        for &x in f64_input.iter() {
381            let (m, e) = frexp_f64(x);
382            mantissas.push(m);
383            exponents.push(e);
384        }
385        let m_arr: Array<f64, D> = Array::from_vec(f64_input.dim().clone(), mantissas)?;
386        let e_arr = Array::from_vec(f64_input.dim().clone(), exponents)?;
387        let m_result = unsafe { crate::helpers::reinterpret_array::<f64, T, D>(m_arr) };
388        Ok((m_result, e_arr))
389    } else if TypeId::of::<T>() == TypeId::of::<f32>() {
390        // SAFETY: T is f32, verified by TypeId check above.
391        let f32_input =
392            unsafe { &*std::ptr::from_ref::<Array<T, D>>(input).cast::<Array<f32, D>>() };
393        let mut mantissas = Vec::with_capacity(f32_input.size());
394        let mut exponents = Vec::with_capacity(f32_input.size());
395        for &x in f32_input.iter() {
396            let (m, e) = frexp_f32(x);
397            mantissas.push(m);
398            exponents.push(e);
399        }
400        let m_arr: Array<f32, D> = Array::from_vec(f32_input.dim().clone(), mantissas)?;
401        let e_arr = Array::from_vec(f32_input.dim().clone(), exponents)?;
402        let m_result = unsafe { crate::helpers::reinterpret_array::<f32, T, D>(m_arr) };
403        Ok((m_result, e_arr))
404    } else {
405        // Fallback for other float types: use f64 round-trip
406        let mut mantissas = Vec::with_capacity(input.size());
407        let mut exponents = Vec::with_capacity(input.size());
408        for &x in input.iter() {
409            let x64 = x.to_f64().unwrap();
410            let (m64, e) = frexp_f64(x64);
411            mantissas.push(T::from(m64).unwrap());
412            exponents.push(e);
413        }
414        Ok((
415            Array::from_vec(input.dim().clone(), mantissas)?,
416            Array::from_vec(input.dim().clone(), exponents)?,
417        ))
418    }
419}
420
421/// Decompose into fractional and integer parts.
422///
423/// Returns `(fractional, integer)` where each output has the same dtype and
424/// shape as the input. The fractional part has the same sign as the input
425/// and absolute value < 1; the integer part is `trunc(x)` (round toward zero).
426///
427/// Analogous to `numpy.modf` and C's `modf`. For NaN inputs both outputs are
428/// NaN; for infinite inputs the fractional part is `±0` and the integer part
429/// is the original infinity.
430///
431/// # Errors
432/// Returns `FerrayError::ShapeMismatch` only via the underlying allocation
433/// path (cannot fail for matched shapes — the inputs share `input.dim()`).
434pub fn modf<T, D>(input: &Array<T, D>) -> FerrayResult<(Array<T, D>, Array<T, D>)>
435where
436    T: Element + Float,
437    D: Dimension,
438{
439    let mut fracs = Vec::with_capacity(input.size());
440    let mut ints = Vec::with_capacity(input.size());
441    for &x in input.iter() {
442        if x.is_nan() {
443            fracs.push(x);
444            ints.push(x);
445        } else if x.is_infinite() {
446            // NumPy: modf(±inf) → (±0, ±inf)
447            let zero = <T as num_traits::Zero>::zero();
448            fracs.push(if x.is_sign_positive() { zero } else { -zero });
449            ints.push(x);
450        } else {
451            let i = x.trunc();
452            ints.push(i);
453            fracs.push(x - i);
454        }
455    }
456    Ok((
457        Array::from_vec(input.dim().clone(), fracs)?,
458        Array::from_vec(input.dim().clone(), ints)?,
459    ))
460}
461
462/// Elementwise copysign: magnitude of x1, sign of x2.
463pub fn copysign<T, D>(x1: &Array<T, D>, x2: &Array<T, D>) -> FerrayResult<Array<T, D>>
464where
465    T: Element + Float,
466    D: Dimension,
467{
468    binary_elementwise_op(x1, x2, |a, b| {
469        let mag = a.abs();
470        if b.is_sign_negative() { -mag } else { mag }
471    })
472}
473
474/// Float power: x1^x2, always returning float.
475pub fn float_power<T, D>(x1: &Array<T, D>, x2: &Array<T, D>) -> FerrayResult<Array<T, D>>
476where
477    T: Element + Float,
478    D: Dimension,
479{
480    binary_elementwise_op(x1, x2, T::powf)
481}
482
483/// Elementwise maximum, propagating NaN.
484pub fn maximum<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<T, D>>
485where
486    T: Element + Float,
487    D: Dimension,
488{
489    binary_elementwise_op(a, b, |x, y| {
490        if x.is_nan() || y.is_nan() {
491            <T as Float>::nan()
492        } else if x >= y {
493            x
494        } else {
495            y
496        }
497    })
498}
499
500/// Elementwise minimum, propagating NaN.
501pub fn minimum<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<T, D>>
502where
503    T: Element + Float,
504    D: Dimension,
505{
506    binary_elementwise_op(a, b, |x, y| {
507        if x.is_nan() || y.is_nan() {
508            <T as Float>::nan()
509        } else if x <= y {
510            x
511        } else {
512            y
513        }
514    })
515}
516
517/// Elementwise maximum, ignoring NaN.
518pub fn fmax<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<T, D>>
519where
520    T: Element + Float,
521    D: Dimension,
522{
523    binary_elementwise_op(a, b, |x, y| {
524        if x.is_nan() {
525            y
526        } else if y.is_nan() || x >= y {
527            x
528        } else {
529            y
530        }
531    })
532}
533
534/// Elementwise minimum, ignoring NaN.
535pub fn fmin<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<T, D>>
536where
537    T: Element + Float,
538    D: Dimension,
539{
540    binary_elementwise_op(a, b, |x, y| {
541        if x.is_nan() {
542            y
543        } else if y.is_nan() || x <= y {
544            x
545        } else {
546            y
547        }
548    })
549}
550
551// ---------------------------------------------------------------------------
552// f16 variants (f32-promoted)
553// ---------------------------------------------------------------------------
554
555/// Elementwise test for NaN for f16 arrays.
556#[cfg(feature = "f16")]
557pub fn isnan_f16<D>(input: &Array<half::f16, D>) -> FerrayResult<Array<bool, D>>
558where
559    D: Dimension,
560{
561    crate::helpers::unary_f16_to_bool_op(input, f32::is_nan)
562}
563
564/// Elementwise test for infinity for f16 arrays.
565#[cfg(feature = "f16")]
566pub fn isinf_f16<D>(input: &Array<half::f16, D>) -> FerrayResult<Array<bool, D>>
567where
568    D: Dimension,
569{
570    crate::helpers::unary_f16_to_bool_op(input, f32::is_infinite)
571}
572
573/// Elementwise test for finiteness for f16 arrays.
574#[cfg(feature = "f16")]
575pub fn isfinite_f16<D>(input: &Array<half::f16, D>) -> FerrayResult<Array<bool, D>>
576where
577    D: Dimension,
578{
579    crate::helpers::unary_f16_to_bool_op(input, f32::is_finite)
580}
581
582/// Clip (limit) values to [`a_min`, `a_max`] for f16 arrays via f32 promotion.
583#[cfg(feature = "f16")]
584pub fn clip_f16<D>(
585    input: &Array<half::f16, D>,
586    a_min: half::f16,
587    a_max: half::f16,
588) -> FerrayResult<Array<half::f16, D>>
589where
590    D: Dimension,
591{
592    let min_f32 = a_min.to_f32();
593    let max_f32 = a_max.to_f32();
594    if min_f32 > max_f32 {
595        return Err(FerrayError::invalid_value("clip: a_min must be <= a_max"));
596    }
597    crate::helpers::unary_f16_op(input, |x| {
598        if x < min_f32 {
599            min_f32
600        } else if x > max_f32 {
601            max_f32
602        } else {
603            x
604        }
605    })
606}
607
608/// Replace NaN with zero, and infinity with large finite numbers, for f16 arrays.
609#[cfg(feature = "f16")]
610pub fn nan_to_num_f16<D>(
611    input: &Array<half::f16, D>,
612    nan: Option<half::f16>,
613    posinf: Option<half::f16>,
614    neginf: Option<half::f16>,
615) -> FerrayResult<Array<half::f16, D>>
616where
617    D: Dimension,
618{
619    let nan_val = nan.unwrap_or(half::f16::ZERO).to_f32();
620    let posinf_val = posinf.unwrap_or(half::f16::MAX).to_f32();
621    let neginf_val = neginf.unwrap_or(half::f16::MIN).to_f32();
622    crate::helpers::unary_f16_op(input, |x| {
623        if x.is_nan() {
624            nan_val
625        } else if x.is_infinite() {
626            if x > 0.0 { posinf_val } else { neginf_val }
627        } else {
628            x
629        }
630    })
631}
632
633/// Elementwise maximum for f16 arrays via f32 promotion, propagating NaN.
634#[cfg(feature = "f16")]
635pub fn maximum_f16<D>(
636    a: &Array<half::f16, D>,
637    b: &Array<half::f16, D>,
638) -> FerrayResult<Array<half::f16, D>>
639where
640    D: Dimension,
641{
642    crate::helpers::binary_f16_op(a, b, |x, y| {
643        if x.is_nan() || y.is_nan() {
644            f32::NAN
645        } else if x >= y {
646            x
647        } else {
648            y
649        }
650    })
651}
652
653/// Elementwise minimum for f16 arrays via f32 promotion, propagating NaN.
654#[cfg(feature = "f16")]
655pub fn minimum_f16<D>(
656    a: &Array<half::f16, D>,
657    b: &Array<half::f16, D>,
658) -> FerrayResult<Array<half::f16, D>>
659where
660    D: Dimension,
661{
662    crate::helpers::binary_f16_op(a, b, |x, y| {
663        if x.is_nan() || y.is_nan() {
664            f32::NAN
665        } else if x <= y {
666            x
667        } else {
668            y
669        }
670    })
671}
672
673#[cfg(test)]
674mod tests {
675    use super::*;
676    use ferray_core::dimension::Ix1;
677
678    use crate::test_util::arr1;
679
680    fn arr1_i32(data: Vec<i32>) -> Array<i32, Ix1> {
681        let n = data.len();
682        Array::from_vec(Ix1::new([n]), data).unwrap()
683    }
684
685    #[test]
686    fn test_isnan() {
687        let a = arr1(vec![1.0, f64::NAN, 3.0]);
688        let r = isnan(&a).unwrap();
689        assert_eq!(r.as_slice().unwrap(), &[false, true, false]);
690    }
691
692    #[test]
693    fn test_isinf() {
694        let a = arr1(vec![1.0, f64::INFINITY, f64::NEG_INFINITY]);
695        let r = isinf(&a).unwrap();
696        assert_eq!(r.as_slice().unwrap(), &[false, true, true]);
697    }
698
699    #[test]
700    fn test_isfinite() {
701        let a = arr1(vec![1.0, f64::INFINITY, f64::NAN]);
702        let r = isfinite(&a).unwrap();
703        assert_eq!(r.as_slice().unwrap(), &[true, false, false]);
704    }
705
706    #[test]
707    fn test_isneginf() {
708        let a = arr1(vec![f64::NEG_INFINITY, f64::INFINITY, 0.0]);
709        let r = isneginf(&a).unwrap();
710        assert_eq!(r.as_slice().unwrap(), &[true, false, false]);
711    }
712
713    #[test]
714    fn test_isposinf() {
715        let a = arr1(vec![f64::NEG_INFINITY, f64::INFINITY, 0.0]);
716        let r = isposinf(&a).unwrap();
717        assert_eq!(r.as_slice().unwrap(), &[false, true, false]);
718    }
719
720    #[test]
721    fn test_nan_to_num() {
722        let a = arr1(vec![f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1.0]);
723        let r = nan_to_num(&a, None, None, None).unwrap();
724        let s = r.as_slice().unwrap();
725        assert_eq!(s[0], 0.0);
726        assert!(s[1].is_finite() && s[1] > 0.0);
727        assert!(s[2].is_finite() && s[2] < 0.0);
728        assert_eq!(s[3], 1.0);
729    }
730
731    #[test]
732    fn test_clip() {
733        let a = arr1(vec![-1.0, 0.5, 1.5, 3.0]);
734        let r = clip(&a, 0.0, 2.0).unwrap();
735        assert_eq!(r.as_slice().unwrap(), &[0.0, 0.5, 1.5, 2.0]);
736    }
737
738    #[test]
739    fn test_clip_ord_integer() {
740        let a = Array::<i32, Ix1>::from_vec(Ix1::new([5]), vec![-10, 0, 5, 100, 255]).unwrap();
741        let r = clip_ord(&a, 0, 200).unwrap();
742        assert_eq!(r.as_slice().unwrap(), &[0, 0, 5, 100, 200]);
743    }
744
745    #[test]
746    fn test_clip_ord_u8() {
747        let a = Array::<u8, Ix1>::from_vec(Ix1::new([4]), vec![0, 50, 200, 255]).unwrap();
748        let r = clip_ord(&a, 10, 128).unwrap();
749        assert_eq!(r.as_slice().unwrap(), &[10, 50, 128, 128]);
750    }
751
752    #[test]
753    fn test_signbit() {
754        let a = arr1(vec![-1.0, 0.0, 1.0]);
755        let r = signbit(&a).unwrap();
756        assert_eq!(r.as_slice().unwrap(), &[true, false, false]);
757    }
758
759    #[test]
760    fn test_copysign() {
761        let a = arr1(vec![1.0, -2.0, 3.0]);
762        let b = arr1(vec![-1.0, 1.0, -1.0]);
763        let r = copysign(&a, &b).unwrap();
764        assert_eq!(r.as_slice().unwrap(), &[-1.0, 2.0, -3.0]);
765    }
766
767    #[test]
768    fn test_maximum() {
769        let a = arr1(vec![1.0, 5.0, 3.0]);
770        let b = arr1(vec![4.0, 2.0, 3.0]);
771        let r = maximum(&a, &b).unwrap();
772        assert_eq!(r.as_slice().unwrap(), &[4.0, 5.0, 3.0]);
773    }
774
775    #[test]
776    fn test_minimum() {
777        let a = arr1(vec![1.0, 5.0, 3.0]);
778        let b = arr1(vec![4.0, 2.0, 3.0]);
779        let r = minimum(&a, &b).unwrap();
780        assert_eq!(r.as_slice().unwrap(), &[1.0, 2.0, 3.0]);
781    }
782
783    #[test]
784    fn test_maximum_nan_propagation() {
785        let a = arr1(vec![1.0, f64::NAN]);
786        let b = arr1(vec![2.0, 3.0]);
787        let r = maximum(&a, &b).unwrap();
788        let s = r.as_slice().unwrap();
789        assert_eq!(s[0], 2.0);
790        assert!(s[1].is_nan());
791    }
792
793    #[test]
794    fn test_fmax_ignores_nan() {
795        let a = arr1(vec![1.0, f64::NAN]);
796        let b = arr1(vec![2.0, 3.0]);
797        let r = fmax(&a, &b).unwrap();
798        let s = r.as_slice().unwrap();
799        assert_eq!(s[0], 2.0);
800        assert_eq!(s[1], 3.0);
801    }
802
803    #[test]
804    fn test_fmin_ignores_nan() {
805        let a = arr1(vec![1.0, f64::NAN]);
806        let b = arr1(vec![2.0, 3.0]);
807        let r = fmin(&a, &b).unwrap();
808        let s = r.as_slice().unwrap();
809        assert_eq!(s[0], 1.0);
810        assert_eq!(s[1], 3.0);
811    }
812
813    #[test]
814    fn test_float_power() {
815        let a = arr1(vec![2.0, 3.0]);
816        let b = arr1(vec![3.0, 2.0]);
817        let r = float_power(&a, &b).unwrap();
818        assert_eq!(r.as_slice().unwrap(), &[8.0, 9.0]);
819    }
820
821    #[test]
822    fn test_spacing() {
823        let a = arr1(vec![1.0]);
824        let r = spacing(&a).unwrap();
825        let s = r.as_slice().unwrap();
826        // spacing(1.0) == f64::EPSILON (2^-52)
827        assert_eq!(s[0], f64::EPSILON);
828    }
829
830    #[test]
831    fn test_spacing_zero() {
832        let a = arr1(vec![0.0]);
833        let r = spacing(&a).unwrap();
834        let s = r.as_slice().unwrap();
835        // spacing(0.0) == smallest positive subnormal = 5e-324
836        assert_eq!(s[0], f64::from_bits(1));
837    }
838
839    #[test]
840    fn test_spacing_nan_inf() {
841        let a = arr1(vec![f64::NAN, f64::INFINITY, f64::NEG_INFINITY]);
842        let r = spacing(&a).unwrap();
843        let s = r.as_slice().unwrap();
844        assert!(s[0].is_nan());
845        assert!(s[1].is_nan());
846        assert!(s[2].is_nan());
847    }
848
849    #[test]
850    fn test_ldexp() {
851        let x = arr1(vec![1.0, 2.0]);
852        let n = arr1_i32(vec![2, 3]);
853        let r = ldexp(&x, &n).unwrap();
854        let s = r.as_slice().unwrap();
855        assert!((s[0] - 4.0).abs() < 1e-12);
856        assert!((s[1] - 16.0).abs() < 1e-12);
857    }
858
859    #[test]
860    fn test_ldexp_broadcasts() {
861        use ferray_core::dimension::Ix2;
862        let x = Array::<f64, Ix2>::from_vec(Ix2::new([2, 1]), vec![1.0, 3.0]).unwrap();
863        let n = Array::<i32, Ix2>::from_vec(Ix2::new([1, 3]), vec![1, 2, 3]).unwrap();
864        let r = ldexp(&x, &n).unwrap();
865        assert_eq!(r.shape(), &[2, 3]);
866        // 1*2^{1,2,3} = {2,4,8}, 3*2^{1,2,3} = {6,12,24}
867        let v: Vec<f64> = r.iter().copied().collect();
868        assert_eq!(v, vec![2.0, 4.0, 8.0, 6.0, 12.0, 24.0]);
869    }
870
871    #[test]
872    fn test_frexp() {
873        let a = arr1(vec![4.0]);
874        let (m, e) = frexp(&a).unwrap();
875        let ms = m.as_slice().unwrap();
876        let es = e.as_slice().unwrap();
877        // 4 = 0.5 * 2^3
878        assert!((ms[0] - 0.5).abs() < 1e-12);
879        assert_eq!(es[0], 3);
880    }
881
882    #[test]
883    fn test_nextafter_zero_toward_positive() {
884        let a = arr1(vec![0.0]);
885        let b = arr1(vec![1.0]);
886        let r = nextafter(&a, &b).unwrap();
887        let s = r.as_slice().unwrap();
888        // nextafter(0, 1) == smallest positive subnormal = 5e-324
889        assert_eq!(s[0], f64::from_bits(1));
890    }
891
892    #[test]
893    fn test_nextafter_zero_toward_negative() {
894        let a = arr1(vec![0.0]);
895        let b = arr1(vec![-1.0]);
896        let r = nextafter(&a, &b).unwrap();
897        let s = r.as_slice().unwrap();
898        // nextafter(0, -1) == smallest negative subnormal = -5e-324
899        assert_eq!(s[0], f64::from_bits(1u64 | (1u64 << 63)));
900    }
901
902    #[test]
903    fn test_nextafter_equal() {
904        let a = arr1(vec![1.0]);
905        let b = arr1(vec![1.0]);
906        let r = nextafter(&a, &b).unwrap();
907        let s = r.as_slice().unwrap();
908        assert_eq!(s[0], 1.0);
909    }
910
911    #[test]
912    fn test_nextafter_nan() {
913        let a = arr1(vec![f64::NAN, 1.0]);
914        let b = arr1(vec![1.0, f64::NAN]);
915        let r = nextafter(&a, &b).unwrap();
916        let s = r.as_slice().unwrap();
917        assert!(s[0].is_nan());
918        assert!(s[1].is_nan());
919    }
920
921    #[test]
922    fn test_nextafter_one_toward_two() {
923        let a = arr1(vec![1.0]);
924        let b = arr1(vec![2.0]);
925        let r = nextafter(&a, &b).unwrap();
926        let s = r.as_slice().unwrap();
927        // nextafter(1.0, 2.0) == 1.0 + epsilon
928        assert_eq!(s[0], 1.0 + f64::EPSILON);
929    }
930
931    #[test]
932    fn test_nextafter_negative() {
933        let a = arr1(vec![-1.0]);
934        let b = arr1(vec![-2.0]);
935        let r = nextafter(&a, &b).unwrap();
936        let s = r.as_slice().unwrap();
937        // nextafter(-1.0, -2.0) == -1.0 - epsilon
938        assert_eq!(s[0], -1.0 - f64::EPSILON);
939    }
940
941    #[cfg(feature = "f16")]
942    mod f16_tests {
943        use super::*;
944
945        fn arr1_f16(data: &[f32]) -> Array<half::f16, Ix1> {
946            let n = data.len();
947            let vals: Vec<half::f16> = data.iter().map(|&x| half::f16::from_f32(x)).collect();
948            Array::from_vec(Ix1::new([n]), vals).unwrap()
949        }
950
951        #[test]
952        fn test_isnan_f16() {
953            let a = arr1_f16(&[1.0, f32::NAN, 3.0]);
954            let r = isnan_f16(&a).unwrap();
955            assert_eq!(r.as_slice().unwrap(), &[false, true, false]);
956        }
957
958        #[test]
959        fn test_isinf_f16() {
960            let a = arr1_f16(&[1.0, f32::INFINITY, f32::NEG_INFINITY]);
961            let r = isinf_f16(&a).unwrap();
962            assert_eq!(r.as_slice().unwrap(), &[false, true, true]);
963        }
964
965        #[test]
966        fn test_isfinite_f16() {
967            let a = arr1_f16(&[1.0, f32::INFINITY, f32::NAN]);
968            let r = isfinite_f16(&a).unwrap();
969            assert_eq!(r.as_slice().unwrap(), &[true, false, false]);
970        }
971
972        #[test]
973        fn test_clip_f16() {
974            let a = arr1_f16(&[-1.0, 0.5, 1.5, 3.0]);
975            let r = clip_f16(&a, half::f16::from_f32(0.0), half::f16::from_f32(2.0)).unwrap();
976            let s = r.as_slice().unwrap();
977            assert!((s[0].to_f32() - 0.0).abs() < 0.01);
978            assert!((s[1].to_f32() - 0.5).abs() < 0.01);
979            assert!((s[2].to_f32() - 1.5).abs() < 0.01);
980            assert!((s[3].to_f32() - 2.0).abs() < 0.01);
981        }
982
983        #[test]
984        fn test_nan_to_num_f16() {
985            let a = arr1_f16(&[f32::NAN, 1.0]);
986            let r = nan_to_num_f16(&a, None, None, None).unwrap();
987            let s = r.as_slice().unwrap();
988            assert_eq!(s[0].to_f32(), 0.0);
989            assert!((s[1].to_f32() - 1.0).abs() < 0.01);
990        }
991
992        #[test]
993        fn test_maximum_f16() {
994            let a = arr1_f16(&[1.0, 5.0, 3.0]);
995            let b = arr1_f16(&[4.0, 2.0, 3.0]);
996            let r = maximum_f16(&a, &b).unwrap();
997            let s = r.as_slice().unwrap();
998            assert!((s[0].to_f32() - 4.0).abs() < 0.01);
999            assert!((s[1].to_f32() - 5.0).abs() < 0.01);
1000            assert!((s[2].to_f32() - 3.0).abs() < 0.01);
1001        }
1002
1003        #[test]
1004        fn test_minimum_f16() {
1005            let a = arr1_f16(&[1.0, 5.0, 3.0]);
1006            let b = arr1_f16(&[4.0, 2.0, 3.0]);
1007            let r = minimum_f16(&a, &b).unwrap();
1008            let s = r.as_slice().unwrap();
1009            assert!((s[0].to_f32() - 1.0).abs() < 0.01);
1010            assert!((s[1].to_f32() - 2.0).abs() < 0.01);
1011            assert!((s[2].to_f32() - 3.0).abs() < 0.01);
1012        }
1013    }
1014
1015    // -- modf --
1016
1017    #[test]
1018    fn test_modf_positive() {
1019        let a = arr1(vec![3.5, 1.25, 0.0]);
1020        let (frac, int) = modf(&a).unwrap();
1021        let f = frac.as_slice().unwrap();
1022        let i = int.as_slice().unwrap();
1023        assert!((f[0] - 0.5).abs() < 1e-12);
1024        assert!((f[1] - 0.25).abs() < 1e-12);
1025        assert_eq!(f[2], 0.0);
1026        assert_eq!(i, &[3.0, 1.0, 0.0]);
1027    }
1028
1029    #[test]
1030    fn test_modf_negative() {
1031        let a = arr1(vec![-3.5, -1.25]);
1032        let (frac, int) = modf(&a).unwrap();
1033        let f = frac.as_slice().unwrap();
1034        let i = int.as_slice().unwrap();
1035        assert!((f[0] - (-0.5)).abs() < 1e-12);
1036        assert!((f[1] - (-0.25)).abs() < 1e-12);
1037        assert_eq!(i, &[-3.0, -1.0]);
1038    }
1039
1040    #[test]
1041    fn test_modf_nan() {
1042        let a = arr1(vec![f64::NAN]);
1043        let (frac, int) = modf(&a).unwrap();
1044        assert!(frac.as_slice().unwrap()[0].is_nan());
1045        assert!(int.as_slice().unwrap()[0].is_nan());
1046    }
1047
1048    #[test]
1049    fn test_modf_infinity() {
1050        let a = arr1(vec![f64::INFINITY, f64::NEG_INFINITY]);
1051        let (frac, int) = modf(&a).unwrap();
1052        let f = frac.as_slice().unwrap();
1053        let i = int.as_slice().unwrap();
1054        assert_eq!(f[0], 0.0);
1055        assert!(f[0].is_sign_positive());
1056        assert_eq!(f[1], 0.0);
1057        assert!(f[1].is_sign_negative());
1058        assert!(i[0].is_infinite() && i[0].is_sign_positive());
1059        assert!(i[1].is_infinite() && i[1].is_sign_negative());
1060    }
1061
1062    #[test]
1063    fn test_modf_f32() {
1064        let a: Array<f32, Ix1> = Array::from_vec(Ix1::new([3]), vec![2.75_f32, -0.5, 1.0]).unwrap();
1065        let (frac, int) = modf(&a).unwrap();
1066        let f = frac.as_slice().unwrap();
1067        let i = int.as_slice().unwrap();
1068        assert!((f[0] - 0.75).abs() < 1e-6);
1069        assert!((f[1] - (-0.5)).abs() < 1e-6);
1070        assert_eq!(f[2], 0.0);
1071        assert_eq!(i, &[2.0_f32, 0.0, 1.0]);
1072    }
1073}