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