## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/vector/ops/rounding.rs

    Line
    Count
    Source
    1
    //! Rounding and sign functions for Vector<f32>
    2
    //!
    3
    //! This module provides rounding, truncation, and sign-related operations:
    4
    //! - Rounding: `floor`, `ceil`, `round`, `trunc`
    5
    //! - Parts: `fract` (fractional part)
    6
    //! - Sign: `signum`, `copysign`, `neg`
    7
    8
    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    9
    use crate::backends::neon::NeonBackend;
    10
    #[cfg(target_arch = "wasm32")]
    11
    use crate::backends::wasm::WasmBackend;
    12
    use crate::backends::VectorBackend;
    13
    use crate::vector::Vector;
    14
    use crate::{dispatch_unary_op, Result, TruenoError};
    15
    16
    impl Vector<f32> {
    17
        /// Computes the floor (round down to nearest integer) of each element.
    18
        ///
    19
        /// # Examples
    20
        ///
    21
        /// ```
    22
        /// use trueno::Vector;
    23
        ///
    24
        /// let v = Vector::from_slice(&[3.7, -2.3, 5.0]);
    25
        /// let result = v.floor()?;
    26
        /// assert_eq!(result.as_slice(), &[3.0, -3.0, 5.0]);
    27
        /// # Ok::<(), trueno::TruenoError>(())
    28
        /// ```
    29
    0
        pub fn floor(&self) -> Result<Vector<f32>> {
    30
    0
            let mut result_data = vec![0.0; self.len()];
    31
    32
    0
            if !self.data.is_empty() {
    33
    0
                dispatch_unary_op!(self.backend, floor, &self.data, &mut result_data);
    34
    0
            }
    35
    36
    0
            Ok(Vector {
    37
    0
                data: result_data,
    38
    0
                backend: self.backend,
    39
    0
            })
    40
    0
        }
    41
    42
        /// Computes the ceiling (round up to nearest integer) of each element.
    43
        ///
    44
        /// # Examples
    45
        ///
    46
        /// ```
    47
        /// use trueno::Vector;
    48
        ///
    49
        /// let v = Vector::from_slice(&[3.2, -2.7, 5.0]);
    50
        /// let result = v.ceil()?;
    51
        /// assert_eq!(result.as_slice(), &[4.0, -2.0, 5.0]);
    52
        /// # Ok::<(), trueno::TruenoError>(())
    53
        /// ```
    54
    0
        pub fn ceil(&self) -> Result<Vector<f32>> {
    55
    0
            let mut result_data = vec![0.0; self.len()];
    56
    57
    0
            if !self.data.is_empty() {
    58
    0
                dispatch_unary_op!(self.backend, ceil, &self.data, &mut result_data);
    59
    0
            }
    60
    61
    0
            Ok(Vector {
    62
    0
                data: result_data,
    63
    0
                backend: self.backend,
    64
    0
            })
    65
    0
        }
    66
    67
        /// Rounds each element to the nearest integer.
    68
        ///
    69
        /// Uses "round half away from zero" strategy:
    70
        /// - 0.5 rounds to 1.0, 1.5 rounds to 2.0, -1.5 rounds to -2.0, etc.
    71
        /// - Positive halfway cases round up, negative halfway cases round down.
    72
        ///
    73
        /// # Examples
    74
        ///
    75
        /// ```
    76
        /// use trueno::Vector;
    77
        ///
    78
        /// let v = Vector::from_slice(&[3.2, 3.7, -2.3, -2.8]);
    79
        /// let result = v.round()?;
    80
        /// assert_eq!(result.as_slice(), &[3.0, 4.0, -2.0, -3.0]);
    81
        /// # Ok::<(), trueno::TruenoError>(())
    82
        /// ```
    83
    0
        pub fn round(&self) -> Result<Vector<f32>> {
    84
    0
            let mut result_data = vec![0.0; self.len()];
    85
    86
    0
            if !self.data.is_empty() {
    87
    0
                dispatch_unary_op!(self.backend, round, &self.data, &mut result_data);
    88
    0
            }
    89
    90
    0
            Ok(Vector {
    91
    0
                data: result_data,
    92
    0
                backend: self.backend,
    93
    0
            })
    94
    0
        }
    95
    96
        /// Truncates each element toward zero (removes fractional part).
    97
        ///
    98
        /// Truncation always moves toward zero:
    99
        /// - Positive values: equivalent to floor() (e.g., 3.7 → 3.0)
    100
        /// - Negative values: equivalent to ceil() (e.g., -3.7 → -3.0)
    101
        /// - This differs from floor() which always rounds down
    102
        ///
    103
        /// # Examples
    104
        ///
    105
        /// ```
    106
        /// use trueno::Vector;
    107
        ///
    108
        /// let v = Vector::from_slice(&[3.7, -2.7, 5.0]);
    109
        /// let result = v.trunc()?;
    110
        /// assert_eq!(result.as_slice(), &[3.0, -2.0, 5.0]);
    111
        /// # Ok::<(), trueno::TruenoError>(())
    112
        /// ```
    113
    0
        pub fn trunc(&self) -> Result<Vector<f32>> {
    114
    0
            let trunc_data: Vec<f32> = self.data.iter().map(|x| x.trunc()).collect();
    115
    0
            Ok(Vector {
    116
    0
                data: trunc_data,
    117
    0
                backend: self.backend,
    118
    0
            })
    119
    0
        }
    120
    121
        /// Returns the fractional part of each element.
    122
        ///
    123
        /// The fractional part has the same sign as the original value:
    124
        /// - Positive: fract(3.7) = 0.7
    125
        /// - Negative: fract(-3.7) = -0.7
    126
        /// - Decomposition property: x = trunc(x) + fract(x)
    127
        ///
    128
        /// # Examples
    129
        ///
    130
        /// ```
    131
        /// use trueno::Vector;
    132
        ///
    133
        /// let v = Vector::from_slice(&[3.7, -2.3, 5.0]);
    134
        /// let result = v.fract()?;
    135
        /// // Fractional parts: 0.7, -0.3, 0.0
    136
        /// assert!((result.as_slice()[0] - 0.7).abs() < 1e-5);
    137
        /// assert!((result.as_slice()[1] - (-0.3)).abs() < 1e-5);
    138
        /// # Ok::<(), trueno::TruenoError>(())
    139
        /// ```
    140
    0
        pub fn fract(&self) -> Result<Vector<f32>> {
    141
    0
            let fract_data: Vec<f32> = self.data.iter().map(|x| x.fract()).collect();
    142
    0
            Ok(Vector {
    143
    0
                data: fract_data,
    144
    0
                backend: self.backend,
    145
    0
            })
    146
    0
        }
    147
    148
        /// Returns the sign of each element.
    149
        ///
    150
        /// Returns:
    151
        /// - `1.0` if the value is positive (including +0.0 and +∞)
    152
        /// - `-1.0` if the value is negative (including -0.0 and -∞)
    153
        /// - `NaN` if the value is NaN
    154
        ///
    155
        /// # Examples
    156
        ///
    157
        /// ```
    158
        /// use trueno::Vector;
    159
        ///
    160
        /// let v = Vector::from_slice(&[5.0, -3.0, 0.0, -0.0]);
    161
        /// let result = v.signum()?;
    162
        /// assert_eq!(result.as_slice(), &[1.0, -1.0, 1.0, -1.0]);
    163
        /// # Ok::<(), trueno::TruenoError>(())
    164
        /// ```
    165
    0
        pub fn signum(&self) -> Result<Vector<f32>> {
    166
    0
            let signum_data: Vec<f32> = self.data.iter().map(|x| x.signum()).collect();
    167
    0
            Ok(Vector {
    168
    0
                data: signum_data,
    169
    0
                backend: self.backend,
    170
    0
            })
    171
    0
        }
    172
    173
        /// Returns a vector with the magnitude of `self` and the sign of `sign`.
    174
        ///
    175
        /// For each element pair, takes the magnitude from `self` and the sign from `sign`.
    176
        /// Equivalent to `abs(self\[i\])` with the sign of `sign\[i\]`.
    177
        ///
    178
        /// # Arguments
    179
        ///
    180
        /// * `sign` - Vector providing the sign for each element
    181
        ///
    182
        /// # Errors
    183
        ///
    184
        /// Returns `TruenoError::SizeMismatch` if vectors have different lengths.
    185
        ///
    186
        /// # Examples
    187
        ///
    188
        /// ```
    189
        /// use trueno::Vector;
    190
        ///
    191
        /// let magnitude = Vector::from_slice(&[5.0, 3.0, 2.0]);
    192
        /// let sign = Vector::from_slice(&[-1.0, 1.0, -1.0]);
    193
        /// let result = magnitude.copysign(&sign)?;
    194
        /// assert_eq!(result.as_slice(), &[-5.0, 3.0, -2.0]);
    195
        /// # Ok::<(), trueno::TruenoError>(())
    196
        /// ```
    197
    0
        pub fn copysign(&self, sign: &Self) -> Result<Vector<f32>> {
    198
    0
            if self.len() != sign.len() {
    199
    0
                return Err(TruenoError::SizeMismatch {
    200
    0
                    expected: self.len(),
    201
    0
                    actual: sign.len(),
    202
    0
                });
    203
    0
            }
    204
    205
    0
            let copysign_data: Vec<f32> = self
    206
    0
                .data
    207
    0
                .iter()
    208
    0
                .zip(sign.data.iter())
    209
    0
                .map(|(mag, sgn)| mag.copysign(*sgn))
    210
    0
                .collect();
    211
    212
    0
            Ok(Vector {
    213
    0
                data: copysign_data,
    214
    0
                backend: self.backend,
    215
    0
            })
    216
    0
        }
    217
    218
        /// Element-wise minimum of two vectors.
    219
        ///
    220
        /// Returns a new vector where each element is the minimum of the corresponding
    221
        /// elements from self and other.
    222
        ///
    223
        /// NaN handling: Prefers non-NaN values (NAN.min(x) = x).
    224
        ///
    225
        /// # Examples
    226
        /// ```
    227
        /// use trueno::Vector;
    228
        /// let a = Vector::from_slice(&[1.0, 5.0, 3.0]);
    229
        /// let b = Vector::from_slice(&[2.0, 3.0, 4.0]);
    230
        /// let result = a.minimum(&b)?;
    231
        /// assert_eq!(result.as_slice(), &[1.0, 3.0, 3.0]);
    232
        /// # Ok::<(), trueno::TruenoError>(())
    233
        /// ```
    234
    0
        pub fn minimum(&self, other: &Self) -> Result<Vector<f32>> {
    235
    0
            if self.len() != other.len() {
    236
    0
                return Err(TruenoError::SizeMismatch {
    237
    0
                    expected: self.len(),
    238
    0
                    actual: other.len(),
    239
    0
                });
    240
    0
            }
    241
    242
    0
            let minimum_data: Vec<f32> = self
    243
    0
                .data
    244
    0
                .iter()
    245
    0
                .zip(other.data.iter())
    246
    0
                .map(|(a, b)| a.min(*b))
    247
    0
                .collect();
    248
    249
    0
            Ok(Vector {
    250
    0
                data: minimum_data,
    251
    0
                backend: self.backend,
    252
    0
            })
    253
    0
        }
    254
    255
        /// Element-wise maximum of two vectors.
    256
        ///
    257
        /// Returns a new vector where each element is the maximum of the corresponding
    258
        /// elements from self and other.
    259
        ///
    260
        /// NaN handling: Prefers non-NaN values (NAN.max(x) = x).
    261
        ///
    262
        /// # Examples
    263
        /// ```
    264
        /// use trueno::Vector;
    265
        /// let a = Vector::from_slice(&[1.0, 5.0, 3.0]);
    266
        /// let b = Vector::from_slice(&[2.0, 3.0, 4.0]);
    267
        /// let result = a.maximum(&b)?;
    268
        /// assert_eq!(result.as_slice(), &[2.0, 5.0, 4.0]);
    269
        /// # Ok::<(), trueno::TruenoError>(())
    270
        /// ```
    271
    0
        pub fn maximum(&self, other: &Self) -> Result<Vector<f32>> {
    272
    0
            if self.len() != other.len() {
    273
    0
                return Err(TruenoError::SizeMismatch {
    274
    0
                    expected: self.len(),
    275
    0
                    actual: other.len(),
    276
    0
                });
    277
    0
            }
    278
    279
    0
            let maximum_data: Vec<f32> = self
    280
    0
                .data
    281
    0
                .iter()
    282
    0
                .zip(other.data.iter())
    283
    0
                .map(|(a, b)| a.max(*b))
    284
    0
                .collect();
    285
    286
    0
            Ok(Vector {
    287
    0
                data: maximum_data,
    288
    0
                backend: self.backend,
    289
    0
            })
    290
    0
        }
    291
    292
        /// Element-wise negation (unary minus).
    293
        ///
    294
        /// Returns a new vector where each element is the negation of the corresponding
    295
        /// element from self.
    296
        ///
    297
        /// Properties: Double negation is identity: -(-x) = x
    298
        ///
    299
        /// # Examples
    300
        /// ```
    301
        /// use trueno::Vector;
    302
        /// let a = Vector::from_slice(&[1.0, -2.0, 3.0]);
    303
        /// let result = a.neg()?;
    304
        /// assert_eq!(result.as_slice(), &[-1.0, 2.0, -3.0]);
    305
        /// # Ok::<(), trueno::TruenoError>(())
    306
        /// ```
    307
    0
        pub fn neg(&self) -> Result<Vector<f32>> {
    308
    0
            let neg_data: Vec<f32> = self.data.iter().map(|x| -x).collect();
    309
    0
            Ok(Vector {
    310
    0
                data: neg_data,
    311
    0
                backend: self.backend,
    312
    0
            })
    313
    0
        }
    314
    }