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}