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, traits::Distance};
13use flint_sys::{
14    fmpq::{fmpq_sgn, fmpq_simplest_between},
15    fmpz::{fmpz_cdiv_q, fmpz_fdiv_q},
16};
17
18impl Q {
19    /// Rounds the given rational [`Q`] down to the next integer [`Z`].
20    ///
21    /// # Examples
22    /// ```
23    /// use qfall_math::rational::Q;
24    /// use qfall_math::integer::Z;
25    ///
26    /// let value = Q::from((5, 2));
27    /// assert_eq!(Z::from(2), value.floor());
28    ///
29    /// let value = Q::from((-5, 2));
30    /// assert_eq!(Z::from(-3), value.floor());
31    ///
32    /// let value = Q::from(2);
33    /// assert_eq!(Z::from(2), value.floor());
34    /// ```
35    pub fn floor(&self) -> Z {
36        let mut out = Z::default();
37        unsafe { fmpz_fdiv_q(&mut out.value, &self.value.num, &self.value.den) };
38        out
39    }
40
41    /// Rounds the given rational [`Q`] up to the next integer [`Z`].
42    ///
43    /// # Examples
44    /// ```
45    /// use qfall_math::rational::Q;
46    /// use qfall_math::integer::Z;
47    ///
48    /// let value = Q::from((5, 2));
49    /// assert_eq!(Z::from(3), value.ceil());
50    ///
51    /// let value = Q::from((-5, 2));
52    /// assert_eq!(Z::from(-2), value.ceil());
53    ///
54    /// let value = Q::from(2);
55    /// assert_eq!(Z::from(2), value.ceil());
56    /// ```
57    pub fn ceil(&self) -> Z {
58        let mut out = Z::default();
59        unsafe { fmpz_cdiv_q(&mut out.value, &self.value.num, &self.value.den) };
60        out
61    }
62
63    /// Rounds the given rational [`Q`] to the closest integer [`Z`].
64    /// If the distance is equal, it rounds up.
65    ///
66    /// # Examples
67    /// ```
68    /// use qfall_math::rational::Q;
69    /// use qfall_math::integer::Z;
70    ///
71    /// let value = Q::from((5, 2));
72    /// assert_eq!(Z::from(3), value.round());
73    ///
74    /// let value = Q::from((-5, 2));
75    /// assert_eq!(Z::from(-2), value.round());
76    ///
77    /// let value = Q::from(2);
78    /// assert_eq!(Z::from(2), value.round());
79    /// ```
80    pub fn round(&self) -> Z {
81        if Q::from(self.floor()).distance(self) < Q::from(0.5) {
82            self.floor()
83        } else {
84            self.ceil()
85        }
86    }
87
88    /// Returns the smallest rational with the smallest denominator in the range
89    /// `\[self - |precision|, self + |precision|\]`.
90    ///
91    /// This function allows to free memory in exchange for the specified loss of
92    /// precision (see Example 3). Be aware that this loss of precision is propagated by
93    /// arithmetic operations and can be significantly increased depending on the
94    /// performed operations.
95    ///
96    /// This function ensures that simplifying does not change the sign of `self`.
97    ///
98    /// Parameters:
99    /// - `precision`: the precision the new value can differ from `self`.
100    ///   Note that the absolute value is relevant, not the sign.
101    ///
102    /// Returns the simplest [`Q`] within the defined range.
103    ///
104    /// # Examples
105    /// ```
106    /// use qfall_math::rational::Q;
107    ///
108    /// let value = Q::from((17, 20));
109    /// let precision = Q::from((1, 20));
110    ///
111    /// let simplified = Q::from((4, 5));
112    /// assert_eq!(simplified, value.simplify(&precision));
113    /// ```
114    ///
115    /// ```
116    /// use qfall_math::rational::Q;
117    ///
118    /// let value = Q::from((3, 2));
119    ///
120    /// assert_eq!(Q::ONE, value.simplify(0.5));
121    /// ```
122    ///
123    /// ## Simplify with reasonable precision loss
124    /// This example uses [`Q::INV_MAX32`], i.e. a loss of precision of at most `1 / 2^31 - 2` behind the decimal point.
125    /// If you require higher precision, [`Q::INV_MAX62`] is available.
126    /// ```
127    /// use qfall_math::rational::Q;
128    /// let value = Q::PI;
129    ///
130    /// let simplified = value.simplify(Q::INV_MAX32);
131    ///
132    /// assert_ne!(&Q::PI, &simplified);
133    /// assert!(&simplified >= &(Q::PI - Q::INV_MAX32));
134    /// assert!(&simplified <= &(Q::PI + Q::INV_MAX32));
135    /// ```
136    pub fn simplify(&self, precision: impl Into<Q>) -> Self {
137        let precision = precision.into();
138
139        let lower = self - &precision;
140        let upper = self + &precision;
141        let mut out = Q::default();
142        unsafe { fmpq_simplest_between(&mut out.value, &lower.value, &upper.value) };
143
144        if unsafe { fmpq_sgn(&self.value) != fmpq_sgn(&out.value) } {
145            return Q::MINUS_ONE * out;
146        }
147
148        out
149    }
150
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    ///
159    /// Returns the rounded value as an [`Z`] or an error if `r < 0`.
160    ///
161    /// # Examples
162    /// ```
163    /// use qfall_math::rational::Q;
164    ///
165    /// let value = Q::from((5, 2));
166    /// let rounded = value.randomized_rounding(3).unwrap();
167    /// ```
168    ///
169    /// # Errors and Failures
170    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
171    ///   if `r < 0`.
172    ///
173    /// This function implements randomized rounding according to:
174    /// - Peikert, C. (2010, August).
175    ///   An efficient and parallel Gaussian sampler for lattices.
176    ///   In: Annual Cryptology Conference (pp. 80-97).
177    ///   <https://link.springer.com/chapter/10.1007/978-3-642-14623-7_5>
178    pub fn randomized_rounding(&self, r: impl Into<Q>) -> Result<Z, MathError> {
179        Z::sample_discrete_gauss(self, r)
180    }
181}
182
183#[cfg(test)]
184mod test_floor {
185    use crate::{integer::Z, rational::Q};
186
187    // Ensure that positive rationals are rounded correctly
188    #[test]
189    fn positive() {
190        let val_1 = Q::from((i64::MAX, 2));
191        let val_2 = Q::from((1, u64::MAX));
192
193        assert_eq!(Z::from((i64::MAX - 1) / 2), val_1.floor());
194        assert_eq!(Z::ZERO, val_2.floor());
195    }
196
197    // Ensure that negative rationals are rounded correctly
198    #[test]
199    fn negative() {
200        let val_1 = Q::from((-i64::MAX, 2));
201        let val_2 = Q::from((-1, u64::MAX));
202
203        assert_eq!(Z::from((-i64::MAX - 1) / 2), val_1.floor());
204        assert_eq!(Z::MINUS_ONE, val_2.floor());
205    }
206}
207
208#[cfg(test)]
209mod test_ceil {
210    use crate::{integer::Z, rational::Q};
211
212    // Ensure that positive rationals are rounded correctly
213    #[test]
214    fn positive() {
215        let val_1 = Q::from((i64::MAX, 2));
216        let val_2 = Q::from((1, u64::MAX));
217
218        assert_eq!(Z::from((i64::MAX - 1) / 2 + 1), val_1.ceil());
219        assert_eq!(Z::ONE, val_2.ceil());
220    }
221
222    // Ensure that negative rationals are rounded correctly
223    #[test]
224    fn negative() {
225        let val_1 = Q::from((-i64::MAX, 2));
226        let val_2 = Q::from((-1, u64::MAX));
227
228        assert_eq!(Z::from((-i64::MAX - 1) / 2 + 1), val_1.ceil());
229        assert_eq!(Z::ZERO, val_2.ceil());
230    }
231}
232
233#[cfg(test)]
234mod test_round {
235    use crate::{integer::Z, rational::Q};
236
237    // Ensure that positive rationals are rounded correctly
238    #[test]
239    fn positive() {
240        let val_1 = Q::from((i64::MAX, 2));
241        let val_2 = Q::from((1, u64::MAX));
242
243        assert_eq!(Z::from((i64::MAX - 1) / 2 + 1), val_1.round());
244        assert_eq!(Z::ZERO, val_2.round());
245    }
246
247    // Ensure that negative rationals are rounded correctly
248    #[test]
249    fn negative() {
250        let val_1 = Q::from((-i64::MAX, 2));
251        let val_2 = Q::from((-1, u64::MAX));
252
253        assert_eq!(Z::from((-i64::MAX - 1) / 2 + 1), val_1.round());
254        assert_eq!(Z::ZERO, val_2.round());
255    }
256}
257
258#[cfg(test)]
259mod test_simplify {
260    use crate::{integer::Z, rational::Q, traits::Distance};
261
262    /// Ensure that negative precision works as expected
263    #[test]
264    fn precision_absolute_value() {
265        let value_1 = Q::from((17, 20));
266        let value_2 = Q::from((-17, 20));
267        let precision = Q::from((-1, 20));
268
269        let simplified_1 = Q::from((4, 5));
270        let simplified_2 = Q::from((-4, 5));
271        assert_eq!(simplified_1, value_1.simplify(&precision));
272        assert_eq!(simplified_2, value_2.simplify(precision));
273    }
274
275    /// Ensure that large values with pointer representations are reduced
276    #[test]
277    fn large_pointer_representation() {
278        let value = Q::from((u64::MAX - 1, u64::MAX));
279        let precision = Q::from((1, u64::MAX));
280
281        assert_eq!(Q::ONE, value.simplify(&precision));
282    }
283
284    /// Ensure that the simplified value stays in range
285    #[test]
286    fn stay_in_precision() {
287        let value = Q::from((i64::MAX - 1, i64::MAX));
288        let precision = Q::from((1, u64::MAX - 1));
289
290        let simplified = value.simplify(&precision);
291        assert!(&value - &precision <= simplified && simplified <= &value + &precision);
292        assert!(Q::from((i64::MAX - 2, i64::MAX)) <= simplified && simplified <= 1);
293    }
294
295    /// Ensure max_bits of denominator are not bigger than 1/2 * precision
296    #[test]
297    fn max_bits_denominator() {
298        let value = Q::PI;
299        let precisions = [Q::INV_MAX8, Q::INV_MAX16, Q::INV_MAX32];
300
301        for precision in precisions {
302            let inv_precision = precision.get_denominator();
303            let inv_precision = inv_precision.div_ceil(2);
304
305            let simplified = value.simplify(&precision);
306            let denominator = simplified.get_denominator();
307
308            assert!(denominator.distance(Z::ZERO) < inv_precision);
309        }
310    }
311
312    /// Ensure that a value which can not be simplified is not changed
313    #[test]
314    fn no_change() {
315        let precision = Q::from((1, u64::MAX - 1));
316
317        assert_eq!(Q::ONE, Q::ONE.simplify(&precision));
318        assert_eq!(Q::MINUS_ONE, Q::MINUS_ONE.simplify(&precision));
319        assert_eq!(Q::ZERO, Q::ZERO.simplify(precision));
320    }
321
322    /// Ensure that no sign change can occurr.
323    #[test]
324    fn no_change_of_sign() {
325        assert!(Q::ZERO < Q::ONE.simplify(2));
326        assert!(Q::ZERO > Q::MINUS_ONE.simplify(2));
327    }
328}
329
330#[cfg(test)]
331mod test_randomized_rounding {
332    use crate::rational::Q;
333
334    /// Ensure that a `r < 0` throws an error
335    #[test]
336    fn negative_r() {
337        let value = Q::from((2, 3));
338        assert!(value.randomized_rounding(-1).is_err());
339    }
340}