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}