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
}