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}