Skip to main content

trueno/vector/ops/
rounding.rs

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"))]
9use crate::backends::neon::NeonBackend;
10#[cfg(target_arch = "wasm32")]
11use crate::backends::wasm::WasmBackend;
12use crate::backends::VectorBackend;
13use crate::vector::Vector;
14use crate::{dispatch_unary_op, Result, TruenoError};
15
16impl 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    pub fn floor(&self) -> Result<Vector<f32>> {
30        // Uninit: backend writes every element before any read.
31        let n = self.len();
32        let mut result_data: Vec<f32> = Vec::with_capacity(n);
33        // SAFETY: Backend writes all elements before any read.
34        unsafe {
35            result_data.set_len(n);
36        }
37
38        if !self.data.is_empty() {
39            dispatch_unary_op!(self.backend, floor, &self.data, &mut result_data);
40        }
41
42        Ok(Vector { data: result_data, backend: self.backend })
43    }
44
45    /// Computes the ceiling (round up to nearest integer) of each element.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// use trueno::Vector;
51    ///
52    /// let v = Vector::from_slice(&[3.2, -2.7, 5.0]);
53    /// let result = v.ceil()?;
54    /// assert_eq!(result.as_slice(), &[4.0, -2.0, 5.0]);
55    /// # Ok::<(), trueno::TruenoError>(())
56    /// ```
57    pub fn ceil(&self) -> Result<Vector<f32>> {
58        // Uninit: backend writes every element before any read.
59        let n = self.len();
60        let mut result_data: Vec<f32> = Vec::with_capacity(n);
61        // SAFETY: Backend writes all elements before any read.
62        unsafe {
63            result_data.set_len(n);
64        }
65
66        if !self.data.is_empty() {
67            dispatch_unary_op!(self.backend, ceil, &self.data, &mut result_data);
68        }
69
70        Ok(Vector { data: result_data, backend: self.backend })
71    }
72
73    /// Rounds each element to the nearest integer.
74    ///
75    /// Uses "round half away from zero" strategy:
76    /// - 0.5 rounds to 1.0, 1.5 rounds to 2.0, -1.5 rounds to -2.0, etc.
77    /// - Positive halfway cases round up, negative halfway cases round down.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use trueno::Vector;
83    ///
84    /// let v = Vector::from_slice(&[3.2, 3.7, -2.3, -2.8]);
85    /// let result = v.round()?;
86    /// assert_eq!(result.as_slice(), &[3.0, 4.0, -2.0, -3.0]);
87    /// # Ok::<(), trueno::TruenoError>(())
88    /// ```
89    pub fn round(&self) -> Result<Vector<f32>> {
90        // Uninit: backend writes every element before any read.
91        let n = self.len();
92        let mut result_data: Vec<f32> = Vec::with_capacity(n);
93        // SAFETY: Backend writes all elements before any read.
94        unsafe {
95            result_data.set_len(n);
96        }
97
98        if !self.data.is_empty() {
99            dispatch_unary_op!(self.backend, round, &self.data, &mut result_data);
100        }
101
102        Ok(Vector { data: result_data, backend: self.backend })
103    }
104
105    /// Truncates each element toward zero (removes fractional part).
106    ///
107    /// Truncation always moves toward zero:
108    /// - Positive values: equivalent to floor() (e.g., 3.7 → 3.0)
109    /// - Negative values: equivalent to ceil() (e.g., -3.7 → -3.0)
110    /// - This differs from floor() which always rounds down
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use trueno::Vector;
116    ///
117    /// let v = Vector::from_slice(&[3.7, -2.7, 5.0]);
118    /// let result = v.trunc()?;
119    /// assert_eq!(result.as_slice(), &[3.0, -2.0, 5.0]);
120    /// # Ok::<(), trueno::TruenoError>(())
121    /// ```
122    pub fn trunc(&self) -> Result<Vector<f32>> {
123        let trunc_data: Vec<f32> = self.data.iter().map(|x| x.trunc()).collect();
124        Ok(Vector { data: trunc_data, backend: self.backend })
125    }
126
127    /// Returns the fractional part of each element.
128    ///
129    /// The fractional part has the same sign as the original value:
130    /// - Positive: fract(3.7) = 0.7
131    /// - Negative: fract(-3.7) = -0.7
132    /// - Decomposition property: x = trunc(x) + fract(x)
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use trueno::Vector;
138    ///
139    /// let v = Vector::from_slice(&[3.7, -2.3, 5.0]);
140    /// let result = v.fract()?;
141    /// // Fractional parts: 0.7, -0.3, 0.0
142    /// assert!((result.as_slice()[0] - 0.7).abs() < 1e-5);
143    /// assert!((result.as_slice()[1] - (-0.3)).abs() < 1e-5);
144    /// # Ok::<(), trueno::TruenoError>(())
145    /// ```
146    pub fn fract(&self) -> Result<Vector<f32>> {
147        let fract_data: Vec<f32> = self.data.iter().map(|x| x.fract()).collect();
148        Ok(Vector { data: fract_data, backend: self.backend })
149    }
150
151    /// Returns the sign of each element.
152    ///
153    /// Returns:
154    /// - `1.0` if the value is positive (including +0.0 and +∞)
155    /// - `-1.0` if the value is negative (including -0.0 and -∞)
156    /// - `NaN` if the value is NaN
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use trueno::Vector;
162    ///
163    /// let v = Vector::from_slice(&[5.0, -3.0, 0.0, -0.0]);
164    /// let result = v.signum()?;
165    /// assert_eq!(result.as_slice(), &[1.0, -1.0, 1.0, -1.0]);
166    /// # Ok::<(), trueno::TruenoError>(())
167    /// ```
168    pub fn signum(&self) -> Result<Vector<f32>> {
169        let signum_data: Vec<f32> = self.data.iter().map(|x| x.signum()).collect();
170        Ok(Vector { data: signum_data, backend: self.backend })
171    }
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    pub fn copysign(&self, sign: &Self) -> Result<Vector<f32>> {
198        if self.len() != sign.len() {
199            return Err(TruenoError::SizeMismatch { expected: self.len(), actual: sign.len() });
200        }
201
202        let copysign_data: Vec<f32> =
203            self.data.iter().zip(sign.data.iter()).map(|(mag, sgn)| mag.copysign(*sgn)).collect();
204
205        Ok(Vector { data: copysign_data, backend: self.backend })
206    }
207
208    /// Element-wise minimum of two vectors.
209    ///
210    /// Returns a new vector where each element is the minimum of the corresponding
211    /// elements from self and other.
212    ///
213    /// NaN handling: Prefers non-NaN values (NAN.min(x) = x).
214    ///
215    /// # Examples
216    /// ```
217    /// use trueno::Vector;
218    /// let a = Vector::from_slice(&[1.0, 5.0, 3.0]);
219    /// let b = Vector::from_slice(&[2.0, 3.0, 4.0]);
220    /// let result = a.minimum(&b)?;
221    /// assert_eq!(result.as_slice(), &[1.0, 3.0, 3.0]);
222    /// # Ok::<(), trueno::TruenoError>(())
223    /// ```
224    pub fn minimum(&self, other: &Self) -> Result<Vector<f32>> {
225        if self.len() != other.len() {
226            return Err(TruenoError::SizeMismatch { expected: self.len(), actual: other.len() });
227        }
228
229        let minimum_data: Vec<f32> =
230            self.data.iter().zip(other.data.iter()).map(|(a, b)| a.min(*b)).collect();
231
232        Ok(Vector { data: minimum_data, backend: self.backend })
233    }
234
235    /// Element-wise maximum of two vectors.
236    ///
237    /// Returns a new vector where each element is the maximum of the corresponding
238    /// elements from self and other.
239    ///
240    /// NaN handling: Prefers non-NaN values (NAN.max(x) = x).
241    ///
242    /// # Examples
243    /// ```
244    /// use trueno::Vector;
245    /// let a = Vector::from_slice(&[1.0, 5.0, 3.0]);
246    /// let b = Vector::from_slice(&[2.0, 3.0, 4.0]);
247    /// let result = a.maximum(&b)?;
248    /// assert_eq!(result.as_slice(), &[2.0, 5.0, 4.0]);
249    /// # Ok::<(), trueno::TruenoError>(())
250    /// ```
251    pub fn maximum(&self, other: &Self) -> Result<Vector<f32>> {
252        if self.len() != other.len() {
253            return Err(TruenoError::SizeMismatch { expected: self.len(), actual: other.len() });
254        }
255
256        let maximum_data: Vec<f32> =
257            self.data.iter().zip(other.data.iter()).map(|(a, b)| a.max(*b)).collect();
258
259        Ok(Vector { data: maximum_data, backend: self.backend })
260    }
261
262    /// Element-wise negation (unary minus).
263    ///
264    /// Returns a new vector where each element is the negation of the corresponding
265    /// element from self.
266    ///
267    /// Properties: Double negation is identity: -(-x) = x
268    ///
269    /// # Examples
270    /// ```
271    /// use trueno::Vector;
272    /// let a = Vector::from_slice(&[1.0, -2.0, 3.0]);
273    /// let result = a.neg()?;
274    /// assert_eq!(result.as_slice(), &[-1.0, 2.0, -3.0]);
275    /// # Ok::<(), trueno::TruenoError>(())
276    /// ```
277    pub fn neg(&self) -> Result<Vector<f32>> {
278        let neg_data: Vec<f32> = self.data.iter().map(|x| -x).collect();
279        Ok(Vector { data: neg_data, backend: self.backend })
280    }
281}