Skip to main content

unsigned_float/
simd.rs

1//! Bulk SIMD operations for the unsigned floating-point storage formats.
2//!
3//! The scalar newtypes intentionally retain their precise, portable
4//! promote/compute/demote contract. SIMD is therefore exposed as an opt-in
5//! slice API: storage lanes are widened into native `f32` (`Uf16`) or `f64`
6//! (`Uf32`) vectors, computed there, and rounded back to the exact scalar
7//! encoding. Normal finite lanes take the register-only bit-expansion path;
8//! subnormals, infinities, NaNs, overflows, and negative results fall back to
9//! the scalar conversion for bit-for-bit compatibility.
10//!
11//! This feature uses nightly `portable_simd`. The chosen lane widths follow
12//! the portable `axnn-cpu` tier: 128-bit F32/F64 vectors by default and
13//! 256-bit vectors when the crate is compiled with AVX2 or AVX-512 enabled.
14
15use core::fmt;
16use core::simd::prelude::*;
17
18use crate::{Uf16, Uf16E5M11, Uf16E6M10, Uf32};
19
20/// F32 lanes processed by the UF16 path in one vector operation.
21#[cfg(any(target_feature = "avx2", target_feature = "avx512f"))]
22pub const UF16_LANES: usize = 8;
23/// F32 lanes processed by the UF16 path in one vector operation.
24#[cfg(not(any(target_feature = "avx2", target_feature = "avx512f")))]
25pub const UF16_LANES: usize = 4;
26
27/// F64 lanes processed by the UF32 path in one vector operation.
28#[cfg(any(target_feature = "avx2", target_feature = "avx512f"))]
29pub const UF32_LANES: usize = 4;
30/// F64 lanes processed by the UF32 path in one vector operation.
31#[cfg(not(any(target_feature = "avx2", target_feature = "avx512f")))]
32pub const UF32_LANES: usize = 2;
33
34/// Invalid source/destination slice relationship passed to a bulk operation.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum SimdError {
37    /// Two input planes that must be elementwise aligned have different sizes.
38    InputLengthMismatch { left: usize, right: usize },
39    /// The output plane does not have one element for each input element.
40    OutputLengthMismatch { input: usize, output: usize },
41}
42
43impl fmt::Display for SimdError {
44    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::InputLengthMismatch { left, right } => {
47                write!(formatter, "SIMD input lengths differ ({left} and {right})")
48            }
49            Self::OutputLengthMismatch { input, output } => {
50                write!(
51                    formatter,
52                    "SIMD output length is {output}, expected {input}"
53                )
54            }
55        }
56    }
57}
58
59trait Uf16Layout: Copy {
60    const EXPONENT_BITS: u32;
61    const MANTISSA_BITS: u32;
62    /// Native F32 exponent corresponding to an encoded exponent of zero.
63    const F32_EXPONENT_BIAS: u32;
64
65    fn from_bits(bits: u16) -> Self;
66    fn to_bits(self) -> u16;
67    fn from_f32(value: f32) -> Self;
68    fn to_f32(self) -> f32;
69}
70
71impl Uf16Layout for Uf16E5M11 {
72    const EXPONENT_BITS: u32 = 5;
73    const MANTISSA_BITS: u32 = 11;
74    const F32_EXPONENT_BIAS: u32 = 112;
75
76    fn from_bits(bits: u16) -> Self {
77        Self::from_bits(bits)
78    }
79
80    fn to_bits(self) -> u16 {
81        self.to_bits()
82    }
83
84    fn from_f32(value: f32) -> Self {
85        Self::from_f32(value)
86    }
87
88    fn to_f32(self) -> f32 {
89        self.to_f32()
90    }
91}
92
93impl Uf16Layout for Uf16E6M10 {
94    const EXPONENT_BITS: u32 = 6;
95    const MANTISSA_BITS: u32 = 10;
96    const F32_EXPONENT_BIAS: u32 = 96;
97
98    fn from_bits(bits: u16) -> Self {
99        Self::from_bits(bits)
100    }
101
102    fn to_bits(self) -> u16 {
103        self.to_bits()
104    }
105
106    fn from_f32(value: f32) -> Self {
107        Self::from_f32(value)
108    }
109
110    fn to_f32(self) -> f32 {
111        self.to_f32()
112    }
113}
114
115fn output_len(input: usize, output: usize) -> Result<(), SimdError> {
116    if input == output {
117        Ok(())
118    } else {
119        Err(SimdError::OutputLengthMismatch { input, output })
120    }
121}
122
123fn binary_len(left: usize, right: usize, output: usize) -> Result<(), SimdError> {
124    if left != right {
125        return Err(SimdError::InputLengthMismatch { left, right });
126    }
127    output_len(left, output)
128}
129
130fn uf16_max_exponent<T: Uf16Layout>() -> u32 {
131    (1 << T::EXPONENT_BITS) - 1
132}
133
134fn can_decode_uf16<T: Uf16Layout>(value: T) -> bool {
135    let exponent = (value.to_bits() as u32 >> T::MANTISSA_BITS) & uf16_max_exponent::<T>();
136    exponent != 0 && exponent != uf16_max_exponent::<T>()
137}
138
139fn can_encode_uf16<T: Uf16Layout>(value: f32) -> bool {
140    let bits = value.to_bits();
141    let exponent = (bits >> 23) & 0xff;
142    let max_normal = uf16_max_exponent::<T>() - 1;
143    bits >> 31 == 0
144        && exponent > T::F32_EXPONENT_BIAS
145        // Keep the largest normal bin scalar: a round carry there can become
146        // infinity, while every lane accepted here stays normal after RNE.
147        && exponent < T::F32_EXPONENT_BIAS + max_normal
148}
149
150fn decode_uf16_fast<T: Uf16Layout>(src: &[T]) -> Simd<f32, UF16_LANES> {
151    debug_assert_eq!(src.len(), UF16_LANES);
152    debug_assert!(src.iter().copied().all(can_decode_uf16::<T>));
153    let raw = Simd::<u32, UF16_LANES>::from_array(core::array::from_fn(|lane| {
154        src[lane].to_bits() as u32
155    }));
156    let bits =
157        (raw << Simd::splat(23 - T::MANTISSA_BITS)) + Simd::splat(T::F32_EXPONENT_BIAS << 23);
158    Simd::<f32, UF16_LANES>::from_bits(bits)
159}
160
161fn encode_uf16_fast<T: Uf16Layout>(src: Simd<f32, UF16_LANES>, dst: &mut [T]) {
162    debug_assert_eq!(dst.len(), UF16_LANES);
163    debug_assert!(src.to_array().into_iter().all(can_encode_uf16::<T>));
164    let bits = src.to_bits();
165    let fraction = bits & Simd::splat(0x007f_ffff_u32);
166    let drop = 23 - T::MANTISSA_BITS;
167    let mantissa = fraction >> Simd::splat(drop);
168    let discarded = fraction & Simd::splat((1_u32 << drop) - 1);
169    // Round-to-nearest-even without a per-lane comparison.
170    let rounding =
171        (discarded + Simd::splat((1_u32 << (drop - 1)) - 1) + (mantissa & Simd::splat(1)))
172            >> Simd::splat(drop);
173    let rounded = mantissa + rounding;
174    let carry = rounded >> Simd::splat(T::MANTISSA_BITS);
175    let exponent = (bits >> Simd::splat(23)) - Simd::splat(T::F32_EXPONENT_BIAS) + carry;
176    let raw = (exponent << Simd::splat(T::MANTISSA_BITS))
177        | (rounded & Simd::splat((1_u32 << T::MANTISSA_BITS) - 1));
178    for (lane, bits) in raw.to_array().into_iter().enumerate() {
179        dst[lane] = T::from_bits(bits as u16);
180    }
181}
182
183fn decode_uf16<T: Uf16Layout>(src: &[T], dst: &mut [f32]) -> Result<(), SimdError> {
184    output_len(src.len(), dst.len())?;
185    let vector_end = src.len() / UF16_LANES * UF16_LANES;
186    for offset in (0..vector_end).step_by(UF16_LANES) {
187        let input = &src[offset..offset + UF16_LANES];
188        if input.iter().copied().all(can_decode_uf16::<T>) {
189            decode_uf16_fast(input).copy_to_slice(&mut dst[offset..]);
190        } else {
191            for lane in 0..UF16_LANES {
192                dst[offset + lane] = input[lane].to_f32();
193            }
194        }
195    }
196    for index in vector_end..src.len() {
197        dst[index] = src[index].to_f32();
198    }
199    Ok(())
200}
201
202fn encode_uf16<T: Uf16Layout>(src: &[f32], dst: &mut [T]) -> Result<(), SimdError> {
203    output_len(src.len(), dst.len())?;
204    let vector_end = src.len() / UF16_LANES * UF16_LANES;
205    for offset in (0..vector_end).step_by(UF16_LANES) {
206        let input = Simd::<f32, UF16_LANES>::from_slice(&src[offset..]);
207        if input.to_array().into_iter().all(can_encode_uf16::<T>) {
208            encode_uf16_fast(input, &mut dst[offset..offset + UF16_LANES]);
209        } else {
210            for lane in 0..UF16_LANES {
211                dst[offset + lane] = T::from_f32(src[offset + lane]);
212            }
213        }
214    }
215    for index in vector_end..src.len() {
216        dst[index] = T::from_f32(src[index]);
217    }
218    Ok(())
219}
220
221fn binary_uf16<T: Uf16Layout>(
222    left: &[T],
223    right: &[T],
224    output: &mut [T],
225    vector: impl Fn(Simd<f32, UF16_LANES>, Simd<f32, UF16_LANES>) -> Simd<f32, UF16_LANES>,
226    scalar: impl Fn(f32, f32) -> f32,
227) -> Result<(), SimdError> {
228    binary_len(left.len(), right.len(), output.len())?;
229    let vector_end = left.len() / UF16_LANES * UF16_LANES;
230    for offset in (0..vector_end).step_by(UF16_LANES) {
231        let lhs = &left[offset..offset + UF16_LANES];
232        let rhs = &right[offset..offset + UF16_LANES];
233        if lhs.iter().copied().all(can_decode_uf16::<T>)
234            && rhs.iter().copied().all(can_decode_uf16::<T>)
235        {
236            let result = vector(decode_uf16_fast(lhs), decode_uf16_fast(rhs));
237            if result.to_array().into_iter().all(can_encode_uf16::<T>) {
238                encode_uf16_fast(result, &mut output[offset..offset + UF16_LANES]);
239                continue;
240            }
241        }
242        for lane in 0..UF16_LANES {
243            output[offset + lane] = T::from_f32(scalar(lhs[lane].to_f32(), rhs[lane].to_f32()));
244        }
245    }
246    for index in vector_end..left.len() {
247        output[index] = T::from_f32(scalar(left[index].to_f32(), right[index].to_f32()));
248    }
249    Ok(())
250}
251
252/// Decode packed [`Uf16`] lanes to F32. Normal finite blocks use SIMD bit expansion.
253pub fn decode_uf16_to_f32(src: &[Uf16], dst: &mut [f32]) -> Result<(), SimdError> {
254    decode_uf16(src, dst)
255}
256
257/// Encode F32 lanes as [`Uf16`] with the scalar constructor's exact RNE behavior.
258pub fn encode_f32_to_uf16(src: &[f32], dst: &mut [Uf16]) -> Result<(), SimdError> {
259    encode_uf16(src, dst)
260}
261
262/// Decode packed [`Uf16E6M10`] lanes to F32.
263pub fn decode_uf16e6m10_to_f32(src: &[Uf16E6M10], dst: &mut [f32]) -> Result<(), SimdError> {
264    decode_uf16(src, dst)
265}
266
267/// Encode F32 lanes as [`Uf16E6M10`] with scalar-equivalent rounding.
268pub fn encode_f32_to_uf16e6m10(src: &[f32], dst: &mut [Uf16E6M10]) -> Result<(), SimdError> {
269    encode_uf16(src, dst)
270}
271
272/// Elementwise [`Uf16`] addition through native F32 SIMD lanes.
273pub fn add_uf16(left: &[Uf16], right: &[Uf16], output: &mut [Uf16]) -> Result<(), SimdError> {
274    binary_uf16(
275        left,
276        right,
277        output,
278        |left, right| left + right,
279        |left, right| left + right,
280    )
281}
282
283/// Elementwise [`Uf16`] subtraction through native F32 SIMD lanes.
284pub fn sub_uf16(left: &[Uf16], right: &[Uf16], output: &mut [Uf16]) -> Result<(), SimdError> {
285    binary_uf16(
286        left,
287        right,
288        output,
289        |left, right| left - right,
290        |left, right| left - right,
291    )
292}
293
294/// Elementwise [`Uf16`] multiplication through native F32 SIMD lanes.
295pub fn mul_uf16(left: &[Uf16], right: &[Uf16], output: &mut [Uf16]) -> Result<(), SimdError> {
296    binary_uf16(
297        left,
298        right,
299        output,
300        |left, right| left * right,
301        |left, right| left * right,
302    )
303}
304
305/// Elementwise [`Uf16`] division through native F32 SIMD lanes.
306pub fn div_uf16(left: &[Uf16], right: &[Uf16], output: &mut [Uf16]) -> Result<(), SimdError> {
307    binary_uf16(
308        left,
309        right,
310        output,
311        |left, right| left / right,
312        |left, right| left / right,
313    )
314}
315
316/// Elementwise [`Uf16E6M10`] addition through native F32 SIMD lanes.
317pub fn add_uf16e6m10(
318    left: &[Uf16E6M10],
319    right: &[Uf16E6M10],
320    output: &mut [Uf16E6M10],
321) -> Result<(), SimdError> {
322    binary_uf16(
323        left,
324        right,
325        output,
326        |left, right| left + right,
327        |left, right| left + right,
328    )
329}
330
331/// Elementwise [`Uf16E6M10`] subtraction through native F32 SIMD lanes.
332pub fn sub_uf16e6m10(
333    left: &[Uf16E6M10],
334    right: &[Uf16E6M10],
335    output: &mut [Uf16E6M10],
336) -> Result<(), SimdError> {
337    binary_uf16(
338        left,
339        right,
340        output,
341        |left, right| left - right,
342        |left, right| left - right,
343    )
344}
345
346/// Elementwise [`Uf16E6M10`] multiplication through native F32 SIMD lanes.
347pub fn mul_uf16e6m10(
348    left: &[Uf16E6M10],
349    right: &[Uf16E6M10],
350    output: &mut [Uf16E6M10],
351) -> Result<(), SimdError> {
352    binary_uf16(
353        left,
354        right,
355        output,
356        |left, right| left * right,
357        |left, right| left * right,
358    )
359}
360
361/// Elementwise [`Uf16E6M10`] division through native F32 SIMD lanes.
362pub fn div_uf16e6m10(
363    left: &[Uf16E6M10],
364    right: &[Uf16E6M10],
365    output: &mut [Uf16E6M10],
366) -> Result<(), SimdError> {
367    binary_uf16(
368        left,
369        right,
370        output,
371        |left, right| left / right,
372        |left, right| left / right,
373    )
374}
375
376fn can_decode_uf32(value: Uf32) -> bool {
377    let exponent = value.to_bits() >> 24;
378    exponent != 0 && exponent != 0xff
379}
380
381fn can_encode_uf32(value: f64) -> bool {
382    let bits = value.to_bits();
383    let exponent = (bits >> 52) & 0x7ff;
384    // An encoded exponent of 254 is kept scalar because rounding may carry to infinity.
385    bits >> 63 == 0 && exponent > 896 && exponent < 1150
386}
387
388fn decode_uf32_fast(src: &[Uf32]) -> Simd<f64, UF32_LANES> {
389    debug_assert_eq!(src.len(), UF32_LANES);
390    debug_assert!(src.iter().copied().all(can_decode_uf32));
391    let raw = Simd::<u64, UF32_LANES>::from_array(core::array::from_fn(|lane| {
392        src[lane].to_bits() as u64
393    }));
394    let bits = (raw << Simd::splat(28)) + Simd::splat(896_u64 << 52);
395    Simd::<f64, UF32_LANES>::from_bits(bits)
396}
397
398fn encode_uf32_fast(src: Simd<f64, UF32_LANES>, dst: &mut [Uf32]) {
399    debug_assert_eq!(dst.len(), UF32_LANES);
400    debug_assert!(src.to_array().into_iter().all(can_encode_uf32));
401    let bits = src.to_bits();
402    let fraction = bits & Simd::splat(0x000f_ffff_ffff_ffff_u64);
403    let mantissa = fraction >> Simd::splat(28);
404    let discarded = fraction & Simd::splat((1_u64 << 28) - 1);
405    let rounding = (discarded + Simd::splat((1_u64 << 27) - 1) + (mantissa & Simd::splat(1)))
406        >> Simd::splat(28);
407    let rounded = mantissa + rounding;
408    let carry = rounded >> Simd::splat(24);
409    let exponent = (bits >> Simd::splat(52)) - Simd::splat(896_u64) + carry;
410    let raw = (exponent << Simd::splat(24)) | (rounded & Simd::splat(0x00ff_ffff_u64));
411    for (lane, bits) in raw.to_array().into_iter().enumerate() {
412        dst[lane] = Uf32::from_bits(bits as u32);
413    }
414}
415
416/// Decode packed [`Uf32`] lanes to F64. Normal finite blocks use SIMD bit expansion.
417pub fn decode_uf32_to_f64(src: &[Uf32], dst: &mut [f64]) -> Result<(), SimdError> {
418    output_len(src.len(), dst.len())?;
419    let vector_end = src.len() / UF32_LANES * UF32_LANES;
420    for offset in (0..vector_end).step_by(UF32_LANES) {
421        let input = &src[offset..offset + UF32_LANES];
422        if input.iter().copied().all(can_decode_uf32) {
423            decode_uf32_fast(input).copy_to_slice(&mut dst[offset..]);
424        } else {
425            for lane in 0..UF32_LANES {
426                dst[offset + lane] = input[lane].to_f64();
427            }
428        }
429    }
430    for index in vector_end..src.len() {
431        dst[index] = src[index].to_f64();
432    }
433    Ok(())
434}
435
436/// Encode F64 lanes as [`Uf32`] with the scalar constructor's exact RNE behavior.
437pub fn encode_f64_to_uf32(src: &[f64], dst: &mut [Uf32]) -> Result<(), SimdError> {
438    output_len(src.len(), dst.len())?;
439    let vector_end = src.len() / UF32_LANES * UF32_LANES;
440    for offset in (0..vector_end).step_by(UF32_LANES) {
441        let input = Simd::<f64, UF32_LANES>::from_slice(&src[offset..]);
442        if input.to_array().into_iter().all(can_encode_uf32) {
443            encode_uf32_fast(input, &mut dst[offset..offset + UF32_LANES]);
444        } else {
445            for lane in 0..UF32_LANES {
446                dst[offset + lane] = Uf32::from_f64(src[offset + lane]);
447            }
448        }
449    }
450    for index in vector_end..src.len() {
451        dst[index] = Uf32::from_f64(src[index]);
452    }
453    Ok(())
454}
455
456fn binary_uf32(
457    left: &[Uf32],
458    right: &[Uf32],
459    output: &mut [Uf32],
460    vector: impl Fn(Simd<f64, UF32_LANES>, Simd<f64, UF32_LANES>) -> Simd<f64, UF32_LANES>,
461    scalar: impl Fn(f64, f64) -> f64,
462) -> Result<(), SimdError> {
463    binary_len(left.len(), right.len(), output.len())?;
464    let vector_end = left.len() / UF32_LANES * UF32_LANES;
465    for offset in (0..vector_end).step_by(UF32_LANES) {
466        let lhs = &left[offset..offset + UF32_LANES];
467        let rhs = &right[offset..offset + UF32_LANES];
468        if lhs.iter().copied().all(can_decode_uf32) && rhs.iter().copied().all(can_decode_uf32) {
469            let result = vector(decode_uf32_fast(lhs), decode_uf32_fast(rhs));
470            if result.to_array().into_iter().all(can_encode_uf32) {
471                encode_uf32_fast(result, &mut output[offset..offset + UF32_LANES]);
472                continue;
473            }
474        }
475        for lane in 0..UF32_LANES {
476            output[offset + lane] = Uf32::from_f64(scalar(lhs[lane].to_f64(), rhs[lane].to_f64()));
477        }
478    }
479    for index in vector_end..left.len() {
480        output[index] = Uf32::from_f64(scalar(left[index].to_f64(), right[index].to_f64()));
481    }
482    Ok(())
483}
484
485/// Elementwise [`Uf32`] addition through native F64 SIMD lanes.
486pub fn add_uf32(left: &[Uf32], right: &[Uf32], output: &mut [Uf32]) -> Result<(), SimdError> {
487    binary_uf32(
488        left,
489        right,
490        output,
491        |left, right| left + right,
492        |left, right| left + right,
493    )
494}
495
496/// Elementwise [`Uf32`] subtraction through native F64 SIMD lanes.
497pub fn sub_uf32(left: &[Uf32], right: &[Uf32], output: &mut [Uf32]) -> Result<(), SimdError> {
498    binary_uf32(
499        left,
500        right,
501        output,
502        |left, right| left - right,
503        |left, right| left - right,
504    )
505}
506
507/// Elementwise [`Uf32`] multiplication through native F64 SIMD lanes.
508pub fn mul_uf32(left: &[Uf32], right: &[Uf32], output: &mut [Uf32]) -> Result<(), SimdError> {
509    binary_uf32(
510        left,
511        right,
512        output,
513        |left, right| left * right,
514        |left, right| left * right,
515    )
516}
517
518/// Elementwise [`Uf32`] division through native F64 SIMD lanes.
519pub fn div_uf32(left: &[Uf32], right: &[Uf32], output: &mut [Uf32]) -> Result<(), SimdError> {
520    binary_uf32(
521        left,
522        right,
523        output,
524        |left, right| left / right,
525        |left, right| left / right,
526    )
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532    use std::vec;
533    use std::vec::Vec;
534
535    fn lcg(state: &mut u64) -> u64 {
536        *state = state
537            .wrapping_mul(6_364_136_223_846_793_005)
538            .wrapping_add(1_442_695_040_888_963_407);
539        *state
540    }
541
542    fn verify_uf16_conversions<T: Uf16Layout>() {
543        let source: Vec<T> = (u16::MIN..=u16::MAX).map(T::from_bits).collect();
544        let mut decoded = vec![0.0; source.len()];
545        decode_uf16(&source, &mut decoded).unwrap();
546        for (value, actual) in source.iter().copied().zip(decoded) {
547            assert_eq!(actual.to_bits(), value.to_f32().to_bits());
548        }
549
550        let mut state = 0x6f_1d_5eed_u64;
551        let mut input = vec![0.0; 32_771];
552        input[0] = 0.0;
553        input[1] = -0.0;
554        input[2] = f32::INFINITY;
555        input[3] = f32::NEG_INFINITY;
556        input[4] = f32::NAN;
557        for value in &mut input[5..] {
558            *value = f32::from_bits(lcg(&mut state) as u32);
559        }
560        let mut encoded = vec![T::from_bits(0); input.len()];
561        encode_uf16(&input, &mut encoded).unwrap();
562        for (value, actual) in input.into_iter().zip(encoded) {
563            assert_eq!(actual.to_bits(), T::from_f32(value).to_bits());
564        }
565    }
566
567    fn verify_uf16_binary<T: Uf16Layout>() {
568        let mut state = 0x9a_6d_ef_41_u64;
569        let left: Vec<T> = (0..(UF16_LANES * 19 + 3))
570            .map(|_| T::from_bits(lcg(&mut state) as u16))
571            .collect();
572        let right: Vec<T> = (0..left.len())
573            .map(|_| T::from_bits(lcg(&mut state) as u16))
574            .collect();
575        let mut output = vec![T::from_bits(0); left.len()];
576
577        binary_uf16(
578            &left,
579            &right,
580            &mut output,
581            |left, right| left + right,
582            |left, right| left + right,
583        )
584        .unwrap();
585        for ((left, right), actual) in left.iter().zip(&right).zip(&output) {
586            assert_eq!(
587                actual.to_bits(),
588                T::from_f32(left.to_f32() + right.to_f32()).to_bits()
589            );
590        }
591
592        binary_uf16(
593            &left,
594            &right,
595            &mut output,
596            |left, right| left - right,
597            |left, right| left - right,
598        )
599        .unwrap();
600        for ((left, right), actual) in left.iter().zip(&right).zip(&output) {
601            assert_eq!(
602                actual.to_bits(),
603                T::from_f32(left.to_f32() - right.to_f32()).to_bits()
604            );
605        }
606
607        binary_uf16(
608            &left,
609            &right,
610            &mut output,
611            |left, right| left * right,
612            |left, right| left * right,
613        )
614        .unwrap();
615        for ((left, right), actual) in left.iter().zip(&right).zip(&output) {
616            assert_eq!(
617                actual.to_bits(),
618                T::from_f32(left.to_f32() * right.to_f32()).to_bits()
619            );
620        }
621
622        binary_uf16(
623            &left,
624            &right,
625            &mut output,
626            |left, right| left / right,
627            |left, right| left / right,
628        )
629        .unwrap();
630        for ((left, right), actual) in left.iter().zip(&right).zip(&output) {
631            assert_eq!(
632                actual.to_bits(),
633                T::from_f32(left.to_f32() / right.to_f32()).to_bits()
634            );
635        }
636    }
637
638    #[test]
639    fn uf16e5m11_bulk_paths_are_bit_exact() {
640        verify_uf16_conversions::<Uf16>();
641        verify_uf16_binary::<Uf16>();
642    }
643
644    #[test]
645    fn uf16e6m10_bulk_paths_are_bit_exact() {
646        verify_uf16_conversions::<Uf16E6M10>();
647        verify_uf16_binary::<Uf16E6M10>();
648    }
649
650    #[test]
651    fn uf32_bulk_paths_are_bit_exact() {
652        let mut state = 0x03_2d_99_ef_u64;
653        let mut source = vec![Uf32::ZERO, Uf32::MIN_POSITIVE, Uf32::INFINITY, Uf32::NAN];
654        source.extend((0..32_767).map(|_| Uf32::from_bits(lcg(&mut state) as u32)));
655        let mut decoded = vec![0.0; source.len()];
656        decode_uf32_to_f64(&source, &mut decoded).unwrap();
657        for (value, actual) in source.iter().copied().zip(decoded) {
658            assert_eq!(actual.to_bits(), value.to_f64().to_bits());
659        }
660
661        let mut encoded_input = vec![0.0; 32_771];
662        encoded_input[0] = 0.0;
663        encoded_input[1] = -0.0;
664        encoded_input[2] = f64::INFINITY;
665        encoded_input[3] = f64::NAN;
666        for value in &mut encoded_input[4..] {
667            *value = f64::from_bits(lcg(&mut state));
668        }
669        let mut encoded = vec![Uf32::ZERO; encoded_input.len()];
670        encode_f64_to_uf32(&encoded_input, &mut encoded).unwrap();
671        for (value, actual) in encoded_input.into_iter().zip(encoded) {
672            assert_eq!(actual.to_bits(), Uf32::from_f64(value).to_bits());
673        }
674
675        let left: Vec<Uf32> = (0..(UF32_LANES * 19 + 1))
676            .map(|_| Uf32::from_bits(lcg(&mut state) as u32))
677            .collect();
678        let right: Vec<Uf32> = (0..left.len())
679            .map(|_| Uf32::from_bits(lcg(&mut state) as u32))
680            .collect();
681        let mut output = vec![Uf32::ZERO; left.len()];
682        macro_rules! assert_uf32_binary {
683            ($vector:expr, $scalar:expr) => {{
684                binary_uf32(&left, &right, &mut output, $vector, $scalar).unwrap();
685                for ((left, right), actual) in left.iter().zip(&right).zip(&output) {
686                    assert_eq!(
687                        actual.to_bits(),
688                        Uf32::from_f64($scalar(left.to_f64(), right.to_f64())).to_bits()
689                    );
690                }
691            }};
692        }
693        assert_uf32_binary!(|left, right| left + right, |left: f64, right: f64| left
694            + right);
695        assert_uf32_binary!(|left, right| left - right, |left: f64, right: f64| left
696            - right);
697        assert_uf32_binary!(|left, right| left * right, |left: f64, right: f64| left
698            * right);
699        assert_uf32_binary!(|left, right| left / right, |left: f64, right: f64| left
700            / right);
701    }
702
703    #[test]
704    fn bulk_operations_reject_mismatched_planes() {
705        let input = [Uf16::ONE; 2];
706        let other = [Uf16::ONE; 1];
707        let mut output = [Uf16::ZERO; 2];
708        assert_eq!(
709            add_uf16(&input, &other, &mut output),
710            Err(SimdError::InputLengthMismatch { left: 2, right: 1 })
711        );
712        let mut short = [0.0; 1];
713        assert_eq!(
714            decode_uf16_to_f32(&input, &mut short),
715            Err(SimdError::OutputLengthMismatch {
716                input: 2,
717                output: 1
718            })
719        );
720    }
721}