bbx_core/
sample.rs

1//! Audio sample type abstraction.
2//!
3//! This module defines the [`Sample`] trait, which abstracts over floating-point
4//! types used for audio processing. This allows blocks and graphs to be generic
5//! over sample precision (`f32` or `f64`).
6
7#![allow(clippy::approx_constant)]
8#![allow(clippy::excessive_precision)]
9
10#[cfg(feature = "simd")]
11use std::simd::{StdFloat, cmp::SimdPartialOrd, f32x4, f64x4, num::SimdFloat};
12use std::{
13    fmt::Debug,
14    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
15};
16
17/// Number of SIMD lanes used for vectorized operations.
18#[cfg(feature = "simd")]
19pub const SIMD_LANES: usize = 4;
20
21/// A floating-point type suitable for audio sample data.
22///
23/// This trait abstracts over `f32` and `f64`, allowing DSP blocks and graphs
24/// to be generic over sample precision. Use `f32` for performance-critical
25/// real-time processing, or `f64` when higher precision is required.
26///
27/// When the `simd` feature is enabled, this trait also provides associated
28/// types and methods for SIMD vectorization.
29pub trait Sample:
30    Debug
31    + Copy
32    + Clone
33    + Send
34    + Sync
35    + Add<Output = Self>
36    + Sub<Output = Self>
37    + Mul<Output = Self>
38    + Div<Output = Self>
39    + Neg<Output = Self>
40    + AddAssign
41    + SubAssign
42    + MulAssign
43    + DivAssign
44    + PartialOrd
45    + PartialEq
46    + 'static
47{
48    /// The zero value for this sample type (silence).
49    const ZERO: Self;
50
51    /// The unit value for this sample type (full scale).
52    const ONE: Self;
53
54    /// Machine epsilon i.e. the difference between
55    /// 1.0 and the next larger representable number.
56    const EPSILON: Self;
57
58    /// Pi (π).
59    const PI: Self;
60
61    /// The reciprocal of pi (1/π).
62    const INV_PI: Self;
63
64    /// Half of pi (π/2).
65    const FRAC_PI_2: Self;
66
67    /// Third of pi (π/3).
68    const FRAC_PI_3: Self;
69
70    /// Quarter of pi (π/4).
71    const FRAC_PI_4: Self;
72
73    /// Tau; full circle constant (τ = 2π).
74    const TAU: Self;
75
76    /// Inverse tau (1/τ = 1/2π).
77    const INV_TAU: Self;
78
79    /// The golden ratio (φ).
80    const PHI: Self;
81
82    /// Euler's number (e).
83    const E: Self;
84
85    /// Square root of 2.
86    const SQRT_2: Self;
87
88    /// Inverse square root of 2.
89    const INV_SQRT_2: Self;
90
91    /// Convert from an `f64` value.
92    fn from_f64(value: f64) -> Self;
93
94    /// Convert to an `f64` value.
95    fn to_f64(self) -> f64;
96
97    /// Returns the absolute value of this sample.
98    fn abs(self) -> Self;
99
100    /// The SIMD vector type for this sample type.
101    ///
102    /// This associated type provides all necessary SIMD operations for DSP processing.
103    #[cfg(feature = "simd")]
104    type Simd: SimdFloat<Scalar = Self>
105        + StdFloat
106        + SimdPartialOrd
107        + Copy
108        + Add<Output = Self::Simd>
109        + Sub<Output = Self::Simd>
110        + Mul<Output = Self::Simd>
111        + Div<Output = Self::Simd>
112        + std::ops::Rem<Output = Self::Simd>;
113
114    /// Create a SIMD vector with all lanes set to the given value.
115    #[cfg(feature = "simd")]
116    fn simd_splat(value: Self) -> Self::Simd;
117
118    /// Load a SIMD vector from a slice (must have at least SIMD_LANES elements).
119    #[cfg(feature = "simd")]
120    fn simd_from_slice(slice: &[Self]) -> Self::Simd;
121
122    /// Convert a SIMD vector to an array.
123    #[cfg(feature = "simd")]
124    fn simd_to_array(simd: Self::Simd) -> [Self; SIMD_LANES];
125
126    /// Select elements based on a greater-than comparison.
127    /// Returns `if_true[i]` where `a[i] > b[i]`, otherwise `if_false[i]`.
128    #[cfg(feature = "simd")]
129    fn simd_select_gt(a: Self::Simd, b: Self::Simd, if_true: Self::Simd, if_false: Self::Simd) -> Self::Simd;
130
131    /// Select elements based on a less-than comparison.
132    /// Returns `if_true[i]` where `a[i] < b[i]`, otherwise `if_false[i]`.
133    #[cfg(feature = "simd")]
134    fn simd_select_lt(a: Self::Simd, b: Self::Simd, if_true: Self::Simd, if_false: Self::Simd) -> Self::Simd;
135
136    /// Returns a SIMD vector with lane offsets [0.0, 1.0, 2.0, 3.0].
137    #[cfg(feature = "simd")]
138    fn simd_lane_offsets() -> Self::Simd;
139}
140
141impl Sample for f32 {
142    const ZERO: Self = 0.0;
143    const ONE: Self = 1.0;
144    const EPSILON: Self = 1.19209290e-07_f32;
145    const PI: Self = 3.14159265358979323846264338327950288_f32;
146    const INV_PI: Self = 0.318309886183790671537767526745028724_f32;
147    const FRAC_PI_2: Self = 1.57079632679489661923132169163975144_f32;
148    const FRAC_PI_3: Self = 1.04719755119659774615421446109316763_f32;
149    const FRAC_PI_4: Self = 0.785398163397448309615660845819875721_f32;
150    const TAU: Self = 6.28318530717958647692528676655900577_f32;
151    const INV_TAU: Self = 0.15915494309189533576882414343516084_f32;
152    const PHI: Self = 1.618033988749894848204586834365638118_f32;
153    const E: Self = 2.71828182845904523536028747135266250_f32;
154    const SQRT_2: Self = 1.41421356237309504880168872420969808_f32;
155    const INV_SQRT_2: Self = 0.707106781186547524400844362104849039_f32;
156
157    #[inline]
158    fn from_f64(value: f64) -> Self {
159        value as f32
160    }
161
162    #[inline]
163    fn to_f64(self) -> f64 {
164        self as f64
165    }
166
167    #[inline]
168    fn abs(self) -> Self {
169        self.abs()
170    }
171
172    #[cfg(feature = "simd")]
173    type Simd = f32x4;
174
175    #[cfg(feature = "simd")]
176    #[inline]
177    fn simd_splat(value: Self) -> Self::Simd {
178        f32x4::splat(value)
179    }
180
181    #[cfg(feature = "simd")]
182    #[inline]
183    fn simd_from_slice(slice: &[Self]) -> Self::Simd {
184        f32x4::from_slice(slice)
185    }
186
187    #[cfg(feature = "simd")]
188    #[inline]
189    fn simd_to_array(simd: Self::Simd) -> [Self; SIMD_LANES] {
190        simd.to_array()
191    }
192
193    #[cfg(feature = "simd")]
194    #[inline]
195    fn simd_select_gt(a: Self::Simd, b: Self::Simd, if_true: Self::Simd, if_false: Self::Simd) -> Self::Simd {
196        a.simd_gt(b).select(if_true, if_false)
197    }
198
199    #[cfg(feature = "simd")]
200    #[inline]
201    fn simd_select_lt(a: Self::Simd, b: Self::Simd, if_true: Self::Simd, if_false: Self::Simd) -> Self::Simd {
202        a.simd_lt(b).select(if_true, if_false)
203    }
204
205    #[cfg(feature = "simd")]
206    #[inline]
207    fn simd_lane_offsets() -> Self::Simd {
208        f32x4::from_array([0.0, 1.0, 2.0, 3.0])
209    }
210}
211
212impl Sample for f64 {
213    const ZERO: Self = 0.0;
214    const ONE: Self = 1.0;
215    const EPSILON: Self = 2.2204460492503131e-16_f64;
216    const PI: Self = 3.14159265358979323846264338327950288_f64;
217    const INV_PI: Self = 0.318309886183790671537767526745028724_f64;
218    const FRAC_PI_2: Self = 1.57079632679489661923132169163975144_f64;
219    const FRAC_PI_3: Self = 1.04719755119659774615421446109316763_f64;
220    const FRAC_PI_4: Self = 0.785398163397448309615660845819875721_f64;
221    const TAU: Self = 6.28318530717958647692528676655900577_f64;
222    const INV_TAU: Self = 0.15915494309189533576882414343516084_f64;
223    const PHI: Self = 1.618033988749894848204586834365638118_f64;
224    const E: Self = 2.71828182845904523536028747135266250_f64;
225    const SQRT_2: Self = 1.41421356237309504880168872420969808_f64;
226    const INV_SQRT_2: Self = 0.707106781186547524400844362104849039_f64;
227
228    #[inline]
229    fn from_f64(value: f64) -> Self {
230        value
231    }
232
233    #[inline]
234    fn to_f64(self) -> f64 {
235        self
236    }
237
238    #[inline]
239    fn abs(self) -> Self {
240        self.abs()
241    }
242
243    #[cfg(feature = "simd")]
244    type Simd = f64x4;
245
246    #[cfg(feature = "simd")]
247    #[inline]
248    fn simd_splat(value: Self) -> Self::Simd {
249        f64x4::splat(value)
250    }
251
252    #[cfg(feature = "simd")]
253    #[inline]
254    fn simd_from_slice(slice: &[Self]) -> Self::Simd {
255        f64x4::from_slice(slice)
256    }
257
258    #[cfg(feature = "simd")]
259    #[inline]
260    fn simd_to_array(simd: Self::Simd) -> [Self; SIMD_LANES] {
261        simd.to_array()
262    }
263
264    #[cfg(feature = "simd")]
265    #[inline]
266    fn simd_select_gt(a: Self::Simd, b: Self::Simd, if_true: Self::Simd, if_false: Self::Simd) -> Self::Simd {
267        a.simd_gt(b).select(if_true, if_false)
268    }
269
270    #[cfg(feature = "simd")]
271    #[inline]
272    fn simd_select_lt(a: Self::Simd, b: Self::Simd, if_true: Self::Simd, if_false: Self::Simd) -> Self::Simd {
273        a.simd_lt(b).select(if_true, if_false)
274    }
275
276    #[cfg(feature = "simd")]
277    #[inline]
278    fn simd_lane_offsets() -> Self::Simd {
279        f64x4::from_array([0.0, 1.0, 2.0, 3.0])
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    fn approx_eq<S: Sample>(a: S, b: S, epsilon: f64) -> bool {
288        (a.to_f64() - b.to_f64()).abs() < epsilon
289    }
290
291    #[test]
292    fn test_f32_constants_accuracy() {
293        let epsilon = 1e-6;
294        assert!(approx_eq(f32::PI, std::f32::consts::PI, epsilon));
295        assert!(approx_eq(f32::TAU, std::f32::consts::TAU, epsilon));
296        assert!(approx_eq(f32::E, std::f32::consts::E, epsilon));
297        assert!(approx_eq(f32::SQRT_2, std::f32::consts::SQRT_2, epsilon));
298        assert!(approx_eq(f32::FRAC_PI_2, std::f32::consts::FRAC_PI_2, epsilon));
299        assert!(approx_eq(f32::FRAC_PI_3, std::f32::consts::FRAC_PI_3, epsilon));
300        assert!(approx_eq(f32::FRAC_PI_4, std::f32::consts::FRAC_PI_4, epsilon));
301    }
302
303    #[test]
304    fn test_f64_constants_accuracy() {
305        let epsilon = 1e-14;
306        assert!(approx_eq(f64::PI, std::f64::consts::PI, epsilon));
307        assert!(approx_eq(f64::TAU, std::f64::consts::TAU, epsilon));
308        assert!(approx_eq(f64::E, std::f64::consts::E, epsilon));
309        assert!(approx_eq(f64::SQRT_2, std::f64::consts::SQRT_2, epsilon));
310        assert!(approx_eq(f64::FRAC_PI_2, std::f64::consts::FRAC_PI_2, epsilon));
311        assert!(approx_eq(f64::FRAC_PI_3, std::f64::consts::FRAC_PI_3, epsilon));
312        assert!(approx_eq(f64::FRAC_PI_4, std::f64::consts::FRAC_PI_4, epsilon));
313    }
314
315    #[test]
316    fn test_derived_constants_f32() {
317        let epsilon = 1e-6;
318        assert!(approx_eq(f32::INV_PI, 1.0 / std::f32::consts::PI, epsilon));
319        assert!(approx_eq(f32::INV_TAU, 1.0 / std::f32::consts::TAU, epsilon));
320        assert!(approx_eq(f32::INV_SQRT_2, 1.0 / std::f32::consts::SQRT_2, epsilon));
321    }
322
323    #[test]
324    fn test_derived_constants_f64() {
325        let epsilon = 1e-14;
326        assert!(approx_eq(f64::INV_PI, 1.0 / std::f64::consts::PI, epsilon));
327        assert!(approx_eq(f64::INV_TAU, 1.0 / std::f64::consts::TAU, epsilon));
328        assert!(approx_eq(f64::INV_SQRT_2, 1.0 / std::f64::consts::SQRT_2, epsilon));
329    }
330
331    #[test]
332    fn test_zero_and_one() {
333        assert_eq!(f32::ZERO, 0.0f32);
334        assert_eq!(f32::ONE, 1.0f32);
335        assert_eq!(f64::ZERO, 0.0f64);
336        assert_eq!(f64::ONE, 1.0f64);
337    }
338
339    #[test]
340    fn test_epsilon_is_small_positive() {
341        assert!(f32::EPSILON > 0.0);
342        assert!(f32::EPSILON < 1e-5);
343        assert!(f64::EPSILON > 0.0);
344        assert!(f64::EPSILON < 1e-14);
345    }
346
347    #[test]
348    fn test_f32_to_f64_conversion() {
349        let values = [0.0f32, 1.0, -1.0, 0.5, -0.5, f32::EPSILON];
350        for v in values {
351            let converted = v.to_f64();
352            assert!((converted - v as f64).abs() < 1e-7);
353        }
354    }
355
356    #[test]
357    fn test_f64_to_f64_identity() {
358        let values = [0.0f64, 1.0, -1.0, 0.5, f64::EPSILON, std::f64::consts::PI];
359        for v in values {
360            assert_eq!(v.to_f64(), v);
361        }
362    }
363
364    #[test]
365    fn test_from_f64_f32() {
366        let values = [0.0f64, 1.0, -1.0, 0.5, -0.5];
367        for v in values {
368            let converted = f32::from_f64(v);
369            assert!((converted as f64 - v).abs() < 1e-6);
370        }
371    }
372
373    #[test]
374    fn test_from_f64_f64_identity() {
375        let values = [0.0f64, 1.0, -1.0, std::f64::consts::PI];
376        for v in values {
377            assert_eq!(f64::from_f64(v), v);
378        }
379    }
380
381    #[test]
382    fn test_roundtrip_f32() {
383        let values = [0.0f32, 1.0, -1.0, 0.5, 0.123456];
384        for v in values {
385            let roundtrip = f32::from_f64(v.to_f64());
386            assert!((roundtrip - v).abs() < f32::EPSILON * 2.0);
387        }
388    }
389
390    #[test]
391    fn test_infinity_handling_f32() {
392        let pos_inf = f32::INFINITY;
393        let neg_inf = f32::NEG_INFINITY;
394        assert!(pos_inf.to_f64().is_infinite());
395        assert!(neg_inf.to_f64().is_infinite());
396        assert!(pos_inf.to_f64() > 0.0);
397        assert!(neg_inf.to_f64() < 0.0);
398    }
399
400    #[test]
401    fn test_infinity_handling_f64() {
402        let pos_inf = f64::INFINITY;
403        let neg_inf = f64::NEG_INFINITY;
404        assert!(pos_inf.to_f64().is_infinite());
405        assert!(neg_inf.to_f64().is_infinite());
406        assert_eq!(f64::from_f64(pos_inf), f64::INFINITY);
407        assert_eq!(f64::from_f64(neg_inf), f64::NEG_INFINITY);
408    }
409
410    #[test]
411    fn test_nan_propagation_f32() {
412        let nan = f32::NAN;
413        assert!(nan.to_f64().is_nan());
414        assert!(f32::from_f64(f64::NAN).is_nan());
415    }
416
417    #[test]
418    fn test_nan_propagation_f64() {
419        let nan = f64::NAN;
420        assert!(nan.to_f64().is_nan());
421        assert!(f64::from_f64(f64::NAN).is_nan());
422    }
423
424    #[test]
425    fn test_denormal_conversion_f32() {
426        let denormal = 1e-40_f32;
427        assert!(denormal != 0.0);
428        let converted = denormal.to_f64();
429        assert!(converted != 0.0);
430        assert!((converted - denormal as f64).abs() < 1e-45);
431    }
432
433    #[test]
434    fn test_denormal_conversion_f64() {
435        let denormal = 1e-310_f64;
436        assert!(denormal != 0.0);
437        assert_eq!(denormal.to_f64(), denormal);
438    }
439
440    #[test]
441    fn test_arithmetic_operations_f32() {
442        let a: f32 = 2.0;
443        let b: f32 = 3.0;
444        assert!(approx_eq(a + b, 5.0f32, 1e-6));
445        assert!(approx_eq(a - b, -1.0f32, 1e-6));
446        assert!(approx_eq(a * b, 6.0f32, 1e-6));
447        assert!(approx_eq(a / b, 2.0 / 3.0, 1e-6));
448        assert!(approx_eq(-a, -2.0f32, 1e-6));
449    }
450
451    #[test]
452    fn test_arithmetic_operations_f64() {
453        let a: f64 = 2.0;
454        let b: f64 = 3.0;
455        assert!(approx_eq(a + b, 5.0f64, 1e-14));
456        assert!(approx_eq(a - b, -1.0f64, 1e-14));
457        assert!(approx_eq(a * b, 6.0f64, 1e-14));
458        assert!(approx_eq(a / b, 2.0 / 3.0, 1e-14));
459        assert!(approx_eq(-a, -2.0f64, 1e-14));
460    }
461
462    #[test]
463    fn test_assign_operations_f32() {
464        let mut v: f32 = 1.0;
465        v += 2.0;
466        assert!(approx_eq(v, 3.0f32, 1e-6));
467        v -= 1.0;
468        assert!(approx_eq(v, 2.0f32, 1e-6));
469        v *= 3.0;
470        assert!(approx_eq(v, 6.0f32, 1e-6));
471        v /= 2.0;
472        assert!(approx_eq(v, 3.0f32, 1e-6));
473    }
474
475    #[test]
476    fn test_assign_operations_f64() {
477        let mut v: f64 = 1.0;
478        v += 2.0;
479        assert!(approx_eq(v, 3.0f64, 1e-14));
480        v -= 1.0;
481        assert!(approx_eq(v, 2.0f64, 1e-14));
482        v *= 3.0;
483        assert!(approx_eq(v, 6.0f64, 1e-14));
484        v /= 2.0;
485        assert!(approx_eq(v, 3.0f64, 1e-14));
486    }
487
488    #[test]
489    fn test_comparison_operations() {
490        assert!(1.0f32 < 2.0f32);
491        assert!(2.0f32 > 1.0f32);
492        assert!(1.0f32 <= 1.0f32);
493        assert!(1.0f32 >= 1.0f32);
494        assert!(1.0f32 == 1.0f32);
495        assert!(1.0f32 != 2.0f32);
496
497        assert!(1.0f64 < 2.0f64);
498        assert!(2.0f64 > 1.0f64);
499    }
500
501    #[test]
502    fn test_phi_golden_ratio() {
503        let epsilon_f32 = 1e-6;
504        let epsilon_f64 = 1e-14;
505        let expected_phi = (1.0 + 5.0_f64.sqrt()) / 2.0;
506        assert!(approx_eq(f32::PHI, expected_phi as f32, epsilon_f32));
507        assert!(approx_eq(f64::PHI, expected_phi, epsilon_f64));
508    }
509
510    #[cfg(feature = "simd")]
511    mod simd_tests {
512        use super::*;
513
514        #[test]
515        fn test_simd_splat_f32() {
516            let vec = f32::simd_splat(2.5);
517            let arr = f32::simd_to_array(vec);
518            for v in arr {
519                assert!((v - 2.5).abs() < 1e-6);
520            }
521        }
522
523        #[test]
524        fn test_simd_splat_f64() {
525            let vec = f64::simd_splat(2.5);
526            let arr = f64::simd_to_array(vec);
527            for v in arr {
528                assert!((v - 2.5).abs() < 1e-14);
529            }
530        }
531
532        #[test]
533        fn test_simd_from_slice_f32() {
534            let data = [1.0f32, 2.0, 3.0, 4.0];
535            let vec = f32::simd_from_slice(&data);
536            let arr = f32::simd_to_array(vec);
537            for (a, b) in arr.iter().zip(data.iter()) {
538                assert!((a - b).abs() < 1e-6);
539            }
540        }
541
542        #[test]
543        fn test_simd_from_slice_f64() {
544            let data = [1.0f64, 2.0, 3.0, 4.0];
545            let vec = f64::simd_from_slice(&data);
546            let arr = f64::simd_to_array(vec);
547            for (a, b) in arr.iter().zip(data.iter()) {
548                assert!((a - b).abs() < 1e-14);
549            }
550        }
551
552        #[test]
553        fn test_simd_lane_offsets_f32() {
554            let offsets = f32::simd_lane_offsets();
555            let arr = f32::simd_to_array(offsets);
556            assert!((arr[0] - 0.0).abs() < 1e-6);
557            assert!((arr[1] - 1.0).abs() < 1e-6);
558            assert!((arr[2] - 2.0).abs() < 1e-6);
559            assert!((arr[3] - 3.0).abs() < 1e-6);
560        }
561
562        #[test]
563        fn test_simd_lane_offsets_f64() {
564            let offsets = f64::simd_lane_offsets();
565            let arr = f64::simd_to_array(offsets);
566            assert!((arr[0] - 0.0).abs() < 1e-14);
567            assert!((arr[1] - 1.0).abs() < 1e-14);
568            assert!((arr[2] - 2.0).abs() < 1e-14);
569            assert!((arr[3] - 3.0).abs() < 1e-14);
570        }
571
572        #[test]
573        fn test_simd_select_gt_f32() {
574            let a = f32::simd_from_slice(&[1.0, 3.0, 2.0, 5.0]);
575            let b = f32::simd_from_slice(&[2.0, 2.0, 2.0, 2.0]);
576            let if_true = f32::simd_splat(10.0);
577            let if_false = f32::simd_splat(0.0);
578            let result = f32::simd_select_gt(a, b, if_true, if_false);
579            let arr = f32::simd_to_array(result);
580            assert!((arr[0] - 0.0).abs() < 1e-6);
581            assert!((arr[1] - 10.0).abs() < 1e-6);
582            assert!((arr[2] - 0.0).abs() < 1e-6);
583            assert!((arr[3] - 10.0).abs() < 1e-6);
584        }
585
586        #[test]
587        fn test_simd_select_lt_f32() {
588            let a = f32::simd_from_slice(&[1.0, 3.0, 2.0, 5.0]);
589            let b = f32::simd_from_slice(&[2.0, 2.0, 2.0, 2.0]);
590            let if_true = f32::simd_splat(10.0);
591            let if_false = f32::simd_splat(0.0);
592            let result = f32::simd_select_lt(a, b, if_true, if_false);
593            let arr = f32::simd_to_array(result);
594            assert!((arr[0] - 10.0).abs() < 1e-6);
595            assert!((arr[1] - 0.0).abs() < 1e-6);
596            assert!((arr[2] - 0.0).abs() < 1e-6);
597            assert!((arr[3] - 0.0).abs() < 1e-6);
598        }
599
600        #[test]
601        fn test_simd_select_gt_f64() {
602            let a = f64::simd_from_slice(&[1.0, 3.0, 2.0, 5.0]);
603            let b = f64::simd_from_slice(&[2.0, 2.0, 2.0, 2.0]);
604            let if_true = f64::simd_splat(10.0);
605            let if_false = f64::simd_splat(0.0);
606            let result = f64::simd_select_gt(a, b, if_true, if_false);
607            let arr = f64::simd_to_array(result);
608            assert!((arr[0] - 0.0).abs() < 1e-14);
609            assert!((arr[1] - 10.0).abs() < 1e-14);
610            assert!((arr[2] - 0.0).abs() < 1e-14);
611            assert!((arr[3] - 10.0).abs() < 1e-14);
612        }
613
614        #[test]
615        fn test_simd_select_lt_f64() {
616            let a = f64::simd_from_slice(&[1.0, 3.0, 2.0, 5.0]);
617            let b = f64::simd_from_slice(&[2.0, 2.0, 2.0, 2.0]);
618            let if_true = f64::simd_splat(10.0);
619            let if_false = f64::simd_splat(0.0);
620            let result = f64::simd_select_lt(a, b, if_true, if_false);
621            let arr = f64::simd_to_array(result);
622            assert!((arr[0] - 10.0).abs() < 1e-14);
623            assert!((arr[1] - 0.0).abs() < 1e-14);
624            assert!((arr[2] - 0.0).abs() < 1e-14);
625            assert!((arr[3] - 0.0).abs() < 1e-14);
626        }
627    }
628}