qfall_math/integer/z/arithmetic/
root.rs

1// Copyright © 2023 Sven Moog
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 provides implementations for calculating roots on [`Z`].
10
11use crate::{error::MathError, integer::Z, rational::Q};
12use flint_sys::fmpz::fmpz_sqrtrem;
13
14impl Z {
15    /// Calculate the square root with a fixed precision.
16    ///
17    /// The error of the result is smaller than ±10⁻⁹.
18    /// The actual result may be more accurate and is the best approximation
19    /// for the resulting denominator.
20    ///
21    /// Returns the square root with a precision of ±10⁻⁹.
22    ///
23    /// # Examples
24    /// ```
25    /// use qfall_math::integer::Z;
26    ///
27    /// let value = Z::from(2);
28    /// let root = value.sqrt();
29    /// ```
30    ///
31    /// # Panics ...
32    /// - if the provided value is negative.
33    pub fn sqrt(&self) -> Q {
34        self.sqrt_precision(&Z::from(500000000)).unwrap()
35    }
36
37    /// Calculate the square root with a specified minimum precision.
38    ///
39    /// The actual result may be more accurate and is the best approximation
40    /// for the resulting denominator.
41    /// The [continued fractions expansion](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Continued_fraction_expansion)
42    /// is used to approximate the square root.
43    ///
44    /// Parameters:
45    /// - `precision` specifies the upper limit of the error as `1/(2*precision)`.
46    ///   The precision must be larger than zero.
47    ///
48    /// Returns the square root with a specified minimum precision or
49    /// an error if the precision is not larger than zero or the parameter
50    /// of the square root is negative.
51    ///
52    /// # Examples
53    /// ```
54    /// use qfall_math::integer::Z;
55    /// use qfall_math::rational::Q;
56    ///
57    /// let value = Z::from(42);
58    /// let precision = Z::from(1000);
59    ///
60    /// let root = value.sqrt_precision(&precision);
61    /// ```
62    ///
63    /// # Errors and Failures
64    /// - Returns a [`MathError`] of type [`MathError::NonPositive`]
65    ///   if the precision is not larger than zero.
66    /// - Returns a [`MathError`] of type [`MathError::NonPositive`]
67    ///   if the parameter of the square root is negative.
68    pub fn sqrt_precision(&self, precision: &Z) -> Result<Q, MathError> {
69        if self < &Z::ZERO {
70            return Err(MathError::NonPositive(format!("{self}")));
71        }
72        if precision <= &Z::ZERO {
73            return Err(MathError::NonPositive(format!("{precision}")));
74        }
75
76        let mut integer_result = Q::default();
77        let remainder = Q::default();
78
79        unsafe {
80            // self = integer_result^2 + remainder
81            fmpz_sqrtrem(
82                &mut integer_result.value.num,
83                &remainder.value.num,
84                &self.value,
85            );
86        }
87
88        if remainder == Q::ZERO {
89            Ok(integer_result)
90        } else {
91            // Implementation of the Continued fraction expansion
92            // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Continued_fraction_expansion
93
94            let two_int_res = &integer_result * Q::from(2);
95            // cargo clippy 0.1.70 falsely marks this clone as redundant.
96            #[allow(clippy::redundant_clone)]
97            let mut temp = two_int_res.clone();
98
99            // After the while loop, temp is inverted
100            // => numerator becomes denominator
101            // => current numerator is current precision
102            while &temp.get_numerator() < precision {
103                // Calling unwrap here is safe because temp can not be zero.
104                // `temp` can not be zero, because it is initialized with a positive
105                // number (`2* integer_result`) and after that just positive numbers
106                // are added. `integer_result` would only be zero for `sqrt(0)`, in
107                // which case `remainder == 0` and therefore, this code would not be
108                // executed.
109                temp = &two_int_res + &temp.inverse().unwrap() * &remainder;
110            }
111
112            Ok(&temp.inverse().unwrap() * &remainder + integer_result)
113        }
114    }
115}
116
117#[cfg(test)]
118mod test_sqrt_precision {
119    use crate::{error::MathError, integer::Z, rational::Q};
120    use std::str::FromStr;
121
122    /// Calculate sqrt of different values  with different precisions and
123    /// assert that the result meets the accuracy boundary.
124    #[test]
125    fn precision_correct() {
126        let values = vec![
127            Z::from(1),
128            Z::from(10),
129            Z::from(100000),
130            Z::from(i64::MAX),
131            Z::from(i64::MAX) * Z::from(i64::MAX),
132        ];
133        let precisions = vec![
134            Z::from(1),
135            Z::from(10),
136            Z::from(100000),
137            Z::from(i64::MAX),
138            Z::from(i64::MAX) * Z::from(i64::MAX),
139        ];
140
141        // Test for all combinations of values and precisions
142        for value in values {
143            for precision in &precisions {
144                let root = value.sqrt_precision(precision).unwrap();
145
146                // Reasoning behind the following lines:
147                // v = value, p = 1/(2*precision), r = root, |e|<= p (actual error)
148                // sqrt_precision(v, precision) = r = sqrt(x) + e
149                // => r^2 = x + 2*sqrt(x)*e + e^2
150                // => r^2-x = 2*sqrt(x)*e + e^2 = difference <= 2*sqrt(x)*p + p^2
151                let p = Q::from((1, precision)) / Q::from(2);
152
153                let root_squared = &root * &root;
154                let difference = root_squared - Q::from(value.clone());
155
156                // Use the root calculated with floating point numbers as
157                // an approximation of sqrt(x).
158                let root_from_float = Q::from((i64::MAX as f64).sqrt());
159                let precision_cmp = &Q::from(2) * &p * root_from_float + &p * &p;
160
161                assert!(difference > (&Q::MINUS_ONE * &precision_cmp));
162                assert!(difference < precision_cmp);
163            }
164        }
165    }
166
167    /// Assert that the root of a negative number results in an error.
168    #[test]
169    fn negative_value() {
170        let value = Z::from(-10);
171
172        let res = value.sqrt_precision(&Z::from(10));
173
174        assert!(res.is_err());
175    }
176
177    /// Assert that a precision smaller than one return an error.
178    #[test]
179    fn non_positive_precision() {
180        let value = Z::from(42);
181        let precisions = vec![Z::from(i64::MIN), Z::from(-10), Z::ZERO];
182
183        for precision in &precisions {
184            let root = value.sqrt_precision(precision);
185
186            assert!(root.is_err());
187        }
188    }
189
190    /// Helper function for tests.
191    /// Assert that the sqrt of `value` results in the value in solutions.
192    /// The precision is increased from 0 to the precision of the largest solution.
193    ///
194    /// Parameters:
195    /// - `value` square root will be calculated on this value
196    /// - `solutions` is a vector containing strings that can be pares as [`Q`] values.
197    ///   The values have to be the a sqrt approximation generated by the
198    ///   continues fraction expansion, starting with the worst approximation.
199    ///
200    /// Returns an empty `Ok` if the action could be performed successfully.
201    /// Otherwise, a [`MathError`] is returned if a type conversion failed.
202    ///
203    /// # Errors and Failures
204    /// - Returns a [`MathError`] if a type conversion failed.
205    ///
206    /// # Panics ...
207    /// - if at any point the calculated solution is not matching
208    ///   the given solution.
209    fn compare_solutions(value: Z, solutions: Vec<&str>) -> Result<(), MathError> {
210        let max_precision = Q::from_str(solutions.last().unwrap())?.get_denominator();
211        let max_precision = i64::try_from(&max_precision)?;
212
213        let mut sol_iter = solutions.iter();
214        let mut current_solution = Q::from_str(sol_iter.next().unwrap())?;
215
216        for precision in 1..max_precision {
217            let precision = Z::from(precision);
218
219            let root = value.sqrt_precision(&precision)?;
220
221            if root != current_solution && precision > current_solution.get_denominator() {
222                current_solution = Q::from_str(sol_iter.next().unwrap())?;
223            }
224            assert_eq!(root, current_solution);
225        }
226        Ok(())
227    }
228
229    /// Checks the sqrt with different precisions against the results from
230    /// the wikipedia article <https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Continued_fraction_expansion>
231    #[test]
232    fn not_too_precise_sqrt_3() {
233        let solutions = vec!["2/1", "5/3", "7/4", "19/11", "26/15", "71/41", "97/56"];
234        let value = Z::from(3);
235
236        compare_solutions(value, solutions).unwrap();
237    }
238
239    /// Checks the sqrt with different precisions against the results from
240    /// the wikipedia article <https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Continued_fraction_expansion>
241    #[test]
242    fn not_too_precise_sqrt_10() {
243        let solutions = vec!["19/6", "117/37"];
244        let value = Z::from(10);
245
246        compare_solutions(value, solutions).unwrap();
247    }
248
249    /// Checks the sqrt with different precisions against the results from
250    /// the wikipedia article <https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Continued_fraction_expansion>
251    #[test]
252    fn not_too_precise_sqrt_2() {
253        let solutions = vec!["3/2", "7/5", "17/12", "41/29", "99/70"];
254        let value = Z::from(2);
255
256        compare_solutions(value, solutions).unwrap();
257    }
258
259    /// Assert that sqrt works correctly for small values and returns the exact
260    /// value independent of the precision.
261    #[test]
262    fn squares_small() {
263        let precisions = vec![
264            Z::from(1),
265            Z::from(10),
266            Z::from(100000),
267            Z::from(i64::MAX),
268            Z::from(i64::MAX) * Z::from(i64::MAX),
269        ];
270        let value = Z::from(42);
271        let square = &value * &value;
272
273        for precision in &precisions {
274            let root = square.sqrt_precision(precision).unwrap();
275
276            assert_eq!(Q::from(42), root);
277        }
278    }
279
280    /// Assert that sqrt works correctly for large values and returns the exact
281    /// value independent of the precision.
282    #[test]
283    fn squares_large() {
284        let precisions = vec![
285            Z::from(1),
286            Z::from(10),
287            Z::from(100000),
288            Z::from(i64::MAX),
289            Z::from(i64::MAX) * Z::from(i64::MAX),
290        ];
291        let value = Z::from(u64::MAX);
292        let square = &value * &value;
293
294        for precision in &precisions {
295            let root = square.sqrt_precision(precision).unwrap();
296
297            assert_eq!(Q::from(u64::MAX), root);
298        }
299    }
300}
301
302#[cfg(test)]
303mod test_sqrt {
304    use crate::{integer::Z, rational::Q};
305
306    /// Assert that sqrt works correctly for small square values.
307    #[test]
308    fn squares_small() {
309        let value = Z::from(24);
310        let square = &value * &value;
311
312        let root = square.sqrt();
313
314        assert_eq!(Q::from(value), root);
315    }
316
317    /// Assert that sqrt works correctly for a large square values.
318    #[test]
319    fn squares_large() {
320        let value = Z::from(u64::MAX - 1);
321        let square = &value * &value;
322
323        let root = square.sqrt();
324
325        assert_eq!(Q::from(value), root);
326    }
327
328    /// Assert that sqrt panics with small negative numbers.
329    #[test]
330    #[should_panic]
331    fn negative_small() {
332        let value = Z::from(i64::MIN);
333
334        let _ = value.sqrt();
335    }
336
337    /// Assert that sqrt panics with large negative numbers.
338    #[test]
339    #[should_panic]
340    fn negative_large() {
341        let value = Z::from(i64::MIN);
342
343        let _ = value.sqrt();
344    }
345}