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}