qfall_math/rational/q/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 [`Q`].
10//!
11//! # Max Error Calculation:
12//! Notation:
13//! - `a/b` is the "correct" result
14//! - `e_a` and `e_b` are the error of `a` and `b` resp.
15//! - `p` is the maximum error of a sqrt on [`Z`] values
16//!   => `|e_a| <= p` and `|e_b| <= p`
17//!
18//! ```Q::sqrt(x/y) = (a+e_a)/(b+e_b) = a/b + (e_a*b - a*e_b)/(b*(b+e_b))```
19//!
20//! The Error is the largest with `e_a = p` and `e_b = -p`:
21//! ```|(e_a*b - a*e_b)/(b*(b+e_b))| <= a/b * (b+1) * p/(b-p)```
22
23use crate::{error::MathError, integer::Z, rational::Q};
24
25impl Q {
26    /// Calculate the square root with a fixed relative precision.
27    ///
28    /// The maximum error can be described by:
29    /// `Q::sqrt(x/y) = a/b` the maximum error to the true square root result
30    /// is limited by `a/b * (b + 1) * 10⁻⁹/(b-10⁻⁹)`
31    /// which is less than `2 * a/b * 10^-9`.
32    /// The actual result may be more accurate.
33    ///
34    /// # Examples
35    /// ```
36    /// use qfall_math::rational::Q;
37    ///
38    /// let value = Q::from((9, 4));
39    /// let root = value.sqrt();
40    ///
41    /// assert_eq!(&root, &Q::from((3, 2)));
42    /// ```
43    ///
44    /// # Panics ...
45    /// - if the provided value is negative.
46    pub fn sqrt(&self) -> Q {
47        let root_numerator = self.get_numerator().sqrt();
48        let root_denominator = self.get_denominator().sqrt();
49
50        root_numerator / root_denominator
51    }
52
53    /// Calculate the square root with a specified minimum precision.
54    ///
55    /// Given `Q::sqrt_precision(x/y, precision) = a/b` the maximum error to
56    /// the true square root result is `a/b * (b + 1) * p/(b-p)`
57    /// with `p = 1/(2*precision)`.
58    /// The actual result may be more accurate.
59    ///
60    /// Parameters:
61    /// - `precision` specifies the upper limit of the error.
62    ///   The precision must larger than zero.
63    ///
64    /// Returns the square root of the value as a [`Q`] instance or a [`MathError`],
65    /// if the precision is not larger than zero, or the parameter of the square root is negative.
66    ///
67    /// # Examples
68    /// ```
69    /// use qfall_math::integer::Z;
70    /// use qfall_math::rational::Q;
71    ///
72    /// let precision = Z::from(1000);
73    /// let value = Q::from((9, 4));
74    ///
75    /// let root = value.sqrt_precision(&precision).unwrap();
76    ///
77    /// assert_eq!(&root, &Q::from((3, 2)));
78    /// ```
79    ///
80    /// # Errors and Failures
81    /// - Returns a [`MathError`] of type [`MathError::NonPositive`]
82    ///   if the precision is not larger than zero.
83    /// - Returns a [`MathError`] of type [`MathError::NonPositive`]
84    ///   if the parameter of the square root is negative.
85    pub fn sqrt_precision(&self, precision: &Z) -> Result<Q, MathError> {
86        let root_numerator = self.get_numerator().sqrt_precision(precision)?;
87        let root_denominator = self.get_denominator().sqrt_precision(precision)?;
88
89        Ok(root_numerator / root_denominator)
90    }
91}
92
93#[cfg(test)]
94mod test_sqrt {
95    use crate::{integer::Z, rational::Q, traits::Pow};
96
97    /// Assert that sqrt of zero works correctly
98    #[test]
99    fn zero() {
100        let zero = Q::ZERO;
101
102        let res = zero.sqrt();
103
104        assert_eq!(res, Q::ZERO);
105    }
106
107    /// Assert that [`Q::sqrt_precision()`] returns an error for negative values.
108    #[test]
109    fn negative_value() {
110        let value = Q::from(-10);
111
112        let res = value.sqrt_precision(&Z::from(10));
113
114        assert!(res.is_err());
115    }
116
117    /// Assert that [`Q::sqrt()`] panics for negative values
118    #[test]
119    #[should_panic]
120    fn negative_value_precision() {
121        let value = Q::from(-10);
122
123        let _ = value.sqrt();
124    }
125
126    /// Assert that [`Q::sqrt`] works correctly for rational numbers with squares in
127    /// numerator and denominator.
128    #[test]
129    fn square_rationals() {
130        let values = vec![
131            Q::ZERO,
132            Q::from((1, 3)),
133            Q::from((10, 3)),
134            Q::from((100000, 3)),
135            Q::from((u64::MAX, 3)),
136            Q::from((u64::MAX, u64::MAX - 1)),
137        ];
138
139        // Test for all combinations of values and precisions
140        for value in values {
141            let value_sqr = &value * &value;
142
143            let root = value_sqr.sqrt();
144
145            assert_eq!(value, root);
146        }
147    }
148
149    /// Calculate the square root of different values with different precisions.
150    /// Assert that the result meets the accuracy boundary.
151    #[test]
152    fn precision_correct() {
153        let values = vec![
154            Q::from((1, 3)),
155            Q::from((10, 3)),
156            Q::from((100000, 3)),
157            Q::from(u64::MAX),
158            Q::from((1, u64::MAX)),
159            Q::from((u64::MAX, u64::MAX - 1)) * Q::from(u64::MAX),
160        ];
161        let precisions = vec![
162            Z::from(1),
163            Z::from(10),
164            Z::from(100000),
165            Z::from(u64::MAX),
166            Z::from(u64::MAX).pow(5).unwrap(),
167        ];
168
169        // Test for all combinations of values and precisions
170        for value in values {
171            // Calculate the root using f64 for comparison later on.
172            let num: f64 = value.get_numerator().to_string().parse().unwrap();
173            let den: f64 = value.get_denominator().to_string().parse().unwrap();
174            let root_float: f64 = (num / den).sqrt();
175            let root_float = Q::from(root_float);
176
177            for precision in &precisions {
178                let root = value.sqrt_precision(precision).unwrap();
179
180                // Explanation of the following lines:
181                // 1. Naming:
182                //   x: value from which the sqrt is taken
183                //   p_q and p_z are the maximum possible error of the sqrt for `Q` and `Z` resp.
184                //   |e|<= p_q: actual error
185                //   sqrt(x) = a/b: true square root result without error, approximated by f64::sqrt(x)
186                //   sqrt_precision(x, precision) = root = sqrt(x) + e (true square root result + error)
187                //
188                // 2. Calculation:
189                //   p_q = a/b * (b+1) * p_z/(b-p_z) (See derivation at the top of this file).
190                //   We square the the root and subtract the original value so that the comparison
191                //   of the precision bounds depends less on the precision of f64::sqrt(x).
192                //   => root^2 = x + 2*sqrt(x)*e + e^2
193                //   => root^2-x = 2*sqrt(x)*e + e^2 = difference <= 2*sqrt(x)*p_q + p_q^2
194                let p_z = Q::from((1, 2 * precision));
195                let p_q = &root_float * (root_float.get_denominator() + 1) * &p_z
196                    / (root_float.get_denominator() - &p_z);
197
198                let root_squared = &root * &root;
199                let error_after_squaring = &root_squared - &value;
200
201                let precision_bound_after_squaring = 2 * &p_q * &root_float + &p_q * &p_q;
202
203                assert!(error_after_squaring > (-1 * &precision_bound_after_squaring));
204                assert!(error_after_squaring < precision_bound_after_squaring);
205            }
206        }
207    }
208}