qfall_math/rational/q/rounding.rs
1// Copyright 2023 Marvin Beckmann
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! This module includes functionality for rounding instances of [`Q`].
10
11use super::Q;
12use crate::{error::MathError, integer::Z, macros::seeded::seedable_function, traits::Distance};
13use flint3_sys::{
14 {fmpq_sgn, fmpq_simplest_between}, {fmpz_cdiv_q, fmpz_fdiv_q},
15};
16
17impl Q {
18 /// Rounds the given rational [`Q`] down to the next integer [`Z`].
19 ///
20 /// # Examples
21 /// ```
22 /// use qfall_math::rational::Q;
23 /// use qfall_math::integer::Z;
24 ///
25 /// let value = Q::from((5, 2));
26 /// assert_eq!(Z::from(2), value.floor());
27 ///
28 /// let value = Q::from((-5, 2));
29 /// assert_eq!(Z::from(-3), value.floor());
30 ///
31 /// let value = Q::from(2);
32 /// assert_eq!(Z::from(2), value.floor());
33 /// ```
34 pub fn floor(&self) -> Z {
35 let mut out = Z::default();
36 unsafe { fmpz_fdiv_q(&mut out.value, &self.value.num, &self.value.den) };
37 out
38 }
39
40 /// Rounds the given rational [`Q`] up to the next integer [`Z`].
41 ///
42 /// # Examples
43 /// ```
44 /// use qfall_math::rational::Q;
45 /// use qfall_math::integer::Z;
46 ///
47 /// let value = Q::from((5, 2));
48 /// assert_eq!(Z::from(3), value.ceil());
49 ///
50 /// let value = Q::from((-5, 2));
51 /// assert_eq!(Z::from(-2), value.ceil());
52 ///
53 /// let value = Q::from(2);
54 /// assert_eq!(Z::from(2), value.ceil());
55 /// ```
56 pub fn ceil(&self) -> Z {
57 let mut out = Z::default();
58 unsafe { fmpz_cdiv_q(&mut out.value, &self.value.num, &self.value.den) };
59 out
60 }
61
62 /// Rounds the given rational [`Q`] to the closest integer [`Z`].
63 /// If the distance is equal, it rounds up.
64 ///
65 /// # Examples
66 /// ```
67 /// use qfall_math::rational::Q;
68 /// use qfall_math::integer::Z;
69 ///
70 /// let value = Q::from((5, 2));
71 /// assert_eq!(Z::from(3), value.round());
72 ///
73 /// let value = Q::from((-5, 2));
74 /// assert_eq!(Z::from(-2), value.round());
75 ///
76 /// let value = Q::from(2);
77 /// assert_eq!(Z::from(2), value.round());
78 /// ```
79 pub fn round(&self) -> Z {
80 if Q::from(self.floor()).distance(self) < Q::from(0.5) {
81 self.floor()
82 } else {
83 self.ceil()
84 }
85 }
86
87 /// Returns the smallest rational with the smallest denominator in the range
88 /// `\[self - |precision|, self + |precision|\]`.
89 ///
90 /// This function allows to free memory in exchange for the specified loss of
91 /// precision (see Example 3). Be aware that this loss of precision is propagated by
92 /// arithmetic operations and can be significantly increased depending on the
93 /// performed operations.
94 ///
95 /// This function ensures that simplifying does not change the sign of `self`.
96 ///
97 /// Parameters:
98 /// - `precision`: the precision the new value can differ from `self`.
99 /// Note that the absolute value is relevant, not the sign.
100 ///
101 /// Returns the simplest [`Q`] within the defined range.
102 ///
103 /// # Examples
104 /// ```
105 /// use qfall_math::rational::Q;
106 ///
107 /// let value = Q::from((17, 20));
108 /// let precision = Q::from((1, 20));
109 ///
110 /// let simplified = Q::from((4, 5));
111 /// assert_eq!(simplified, value.simplify(&precision));
112 /// ```
113 ///
114 /// ```
115 /// use qfall_math::rational::Q;
116 ///
117 /// let value = Q::from((3, 2));
118 ///
119 /// assert_eq!(Q::ONE, value.simplify(0.5));
120 /// ```
121 ///
122 /// ## Simplify with reasonable precision loss
123 /// This example uses [`Q::INV_MAX32`], i.e. a loss of precision of at most `1 / 2^31 - 2` behind the decimal point.
124 /// If you require higher precision, [`Q::INV_MAX62`] is available.
125 /// ```
126 /// use qfall_math::rational::Q;
127 /// let value = Q::PI;
128 ///
129 /// let simplified = value.simplify(Q::INV_MAX32);
130 ///
131 /// assert_ne!(&Q::PI, &simplified);
132 /// assert!(&simplified >= &(Q::PI - Q::INV_MAX32));
133 /// assert!(&simplified <= &(Q::PI + Q::INV_MAX32));
134 /// ```
135 pub fn simplify(&self, precision: impl Into<Q>) -> Self {
136 let precision = precision.into();
137
138 let lower = self - &precision;
139 let upper = self + &precision;
140 let mut out = Q::default();
141 unsafe { fmpq_simplest_between(&mut out.value, &lower.value, &upper.value) };
142
143 if unsafe { fmpq_sgn(&self.value) != fmpq_sgn(&out.value) } {
144 return Q::MINUS_ONE * out;
145 }
146
147 out
148 }
149
150 seedable_function!(
151 /// Performs the randomized rounding algorithm
152 /// by sampling from a discrete Gaussian over the integers shifted
153 /// by `self` with gaussian parameter `r`.
154 ///
155 /// Parameters:
156 /// - `r`: specifies the Gaussian parameter, which is proportional
157 /// to the standard deviation `sigma * sqrt(2 * pi) = r`
158 #[seeded]
159 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
160 #[optionally_seeded]
161 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
162 #[optionally_seeded]
163 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
164 ///
165 /// Returns the rounded value as an [`Z`] or an error if `r < 0`.
166 ///
167 /// # Examples
168 /// ```
169 /// use qfall_math::rational::Q;
170 ///
171 /// let value = Q::from((5, 2));
172 #[unseeded]
173 /// let rounded = value.randomized_rounding(3).unwrap();
174 #[seeded]
175 /// let rounded = value.randomized_rounding_seeded(3, [42; 32]).unwrap();
176 /// ```
177 ///
178 /// # Errors and Failures
179 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
180 /// if `r < 0`.
181 ///
182 /// This function implements randomized rounding according to:
183 /// - Peikert, C. (2010, August).
184 /// An efficient and parallel Gaussian sampler for lattices.
185 /// In: Annual Cryptology Conference (pp. 80-97).
186 /// <https://link.springer.com/chapter/10.1007/978-3-642-14623-7_5>
187 pub(crate) fn randomized_rounding(
188 &self,
189 r: impl Into<Q>,
190 seed: Option<[u8; 32]>,
191 ) -> Result<Z, MathError> {
192 Z::sample_discrete_gauss_optionally_seeded(self, r, seed)
193 }
194 );
195}
196
197#[cfg(test)]
198mod test_floor {
199 use crate::{integer::Z, rational::Q};
200
201 // Ensure that positive rationals are rounded correctly
202 #[test]
203 fn positive() {
204 let val_1 = Q::from((i64::MAX, 2));
205 let val_2 = Q::from((1, u64::MAX));
206
207 assert_eq!(Z::from((i64::MAX - 1) / 2), val_1.floor());
208 assert_eq!(Z::ZERO, val_2.floor());
209 }
210
211 // Ensure that negative rationals are rounded correctly
212 #[test]
213 fn negative() {
214 let val_1 = Q::from((-i64::MAX, 2));
215 let val_2 = Q::from((-1, u64::MAX));
216
217 assert_eq!(Z::from((-i64::MAX - 1) / 2), val_1.floor());
218 assert_eq!(Z::MINUS_ONE, val_2.floor());
219 }
220}
221
222#[cfg(test)]
223mod test_ceil {
224 use crate::{integer::Z, rational::Q};
225
226 // Ensure that positive rationals are rounded correctly
227 #[test]
228 fn positive() {
229 let val_1 = Q::from((i64::MAX, 2));
230 let val_2 = Q::from((1, u64::MAX));
231
232 assert_eq!(Z::from((i64::MAX - 1) / 2 + 1), val_1.ceil());
233 assert_eq!(Z::ONE, val_2.ceil());
234 }
235
236 // Ensure that negative rationals are rounded correctly
237 #[test]
238 fn negative() {
239 let val_1 = Q::from((-i64::MAX, 2));
240 let val_2 = Q::from((-1, u64::MAX));
241
242 assert_eq!(Z::from((-i64::MAX - 1) / 2 + 1), val_1.ceil());
243 assert_eq!(Z::ZERO, val_2.ceil());
244 }
245}
246
247#[cfg(test)]
248mod test_round {
249 use crate::{integer::Z, rational::Q};
250
251 // Ensure that positive rationals are rounded correctly
252 #[test]
253 fn positive() {
254 let val_1 = Q::from((i64::MAX, 2));
255 let val_2 = Q::from((1, u64::MAX));
256
257 assert_eq!(Z::from((i64::MAX - 1) / 2 + 1), val_1.round());
258 assert_eq!(Z::ZERO, val_2.round());
259 }
260
261 // Ensure that negative rationals are rounded correctly
262 #[test]
263 fn negative() {
264 let val_1 = Q::from((-i64::MAX, 2));
265 let val_2 = Q::from((-1, u64::MAX));
266
267 assert_eq!(Z::from((-i64::MAX - 1) / 2 + 1), val_1.round());
268 assert_eq!(Z::ZERO, val_2.round());
269 }
270}
271
272#[cfg(test)]
273mod test_simplify {
274 use crate::{integer::Z, rational::Q, traits::Distance};
275
276 /// Ensure that negative precision works as expected
277 #[test]
278 fn precision_absolute_value() {
279 let value_1 = Q::from((17, 20));
280 let value_2 = Q::from((-17, 20));
281 let precision = Q::from((-1, 20));
282
283 let simplified_1 = Q::from((4, 5));
284 let simplified_2 = Q::from((-4, 5));
285 assert_eq!(simplified_1, value_1.simplify(&precision));
286 assert_eq!(simplified_2, value_2.simplify(precision));
287 }
288
289 /// Ensure that large values with pointer representations are reduced
290 #[test]
291 fn large_pointer_representation() {
292 let value = Q::from((u64::MAX - 1, u64::MAX));
293 let precision = Q::from((1, u64::MAX));
294
295 assert_eq!(Q::ONE, value.simplify(&precision));
296 }
297
298 /// Ensure that the simplified value stays in range
299 #[test]
300 fn stay_in_precision() {
301 let value = Q::from((i64::MAX - 1, i64::MAX));
302 let precision = Q::from((1, u64::MAX - 1));
303
304 let simplified = value.simplify(&precision);
305 assert!(&value - &precision <= simplified && simplified <= &value + &precision);
306 assert!(Q::from((i64::MAX - 2, i64::MAX)) <= simplified && simplified <= 1);
307 }
308
309 /// Ensure max_bits of denominator are not bigger than 1/2 * precision
310 #[test]
311 fn max_bits_denominator() {
312 let value = Q::PI;
313 let precisions = [Q::INV_MAX8, Q::INV_MAX16, Q::INV_MAX32];
314
315 for precision in precisions {
316 let inv_precision = precision.get_denominator();
317 let inv_precision = inv_precision.div_ceil(2);
318
319 let simplified = value.simplify(&precision);
320 let denominator = simplified.get_denominator();
321
322 assert!(denominator.distance(Z::ZERO) < inv_precision);
323 }
324 }
325
326 /// Ensure that a value which can not be simplified is not changed
327 #[test]
328 fn no_change() {
329 let precision = Q::from((1, u64::MAX - 1));
330
331 assert_eq!(Q::ONE, Q::ONE.simplify(&precision));
332 assert_eq!(Q::MINUS_ONE, Q::MINUS_ONE.simplify(&precision));
333 assert_eq!(Q::ZERO, Q::ZERO.simplify(precision));
334 }
335
336 /// Ensure that no sign change can occurr.
337 #[test]
338 fn no_change_of_sign() {
339 assert!(Q::ZERO < Q::ONE.simplify(2));
340 assert!(Q::ZERO > Q::MINUS_ONE.simplify(2));
341 }
342}
343
344#[cfg(test)]
345mod test_randomized_rounding {
346 use crate::rational::Q;
347 use crate::utils::sample::test_seed;
348
349 /// Ensure that a `r < 0` throws an error
350 #[test]
351 fn negative_r() {
352 for seeded in [false, true] {
353 let value = Q::from((2, 3));
354 assert!(
355 value
356 .randomized_rounding_optionally_seeded(-1, seeded.then(test_seed))
357 .is_err()
358 );
359 }
360 }
361
362 /// Checks whether the same seed results in the same sample.
363 #[test]
364 fn same_seed_same_sample() {
365 use crate::rational::Q;
366 let value = Q::from((5, 2));
367 let sample_0 = value.randomized_rounding_seeded(3, [42; 32]).unwrap();
368 let sample_1 = value.randomized_rounding_seeded(3, [42; 32]).unwrap();
369
370 assert_eq!(sample_0, sample_1);
371 }
372
373 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
374 #[test]
375 fn known_answer() {
376 use crate::rational::Q;
377 let value = Q::from((5, 2));
378 let sample = value.randomized_rounding_seeded(3, [42; 32]).unwrap();
379
380 assert_eq!("1", sample.to_string());
381 }
382
383 /// Checks whether the unseeded and the seeded function are available.
384 #[test]
385 fn availability() {
386 use crate::rational::Q;
387 let value = Q::from((5, 2));
388
389 // explicitly checks the existence of the unseeded and the seeded function
390 let _ = value.randomized_rounding(3);
391 let _ = value.randomized_rounding_seeded(3, test_seed());
392 }
393}