qfall_math/rational/q/arithmetic/exp.rs
1// Copyright © 2023 Marvin Beckmann and 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//! Implementations to call the exponential function on a [`Q`].
10
11use crate::{
12 rational::{PolyOverQ, Q},
13 traits::Evaluate,
14};
15use flint_sys::fmpq::fmpq_mul_2exp;
16
17impl Q {
18 /// Computes `e^self`.
19 ///
20 /// For exponents below 709, the value is converted to [`f64`] for the calculation.
21 /// As a result, the precision is limited to the precision of [`f64`].
22 /// This means that values smaller than `e^-745` are rounded to `0`.
23 /// The [`f64`] calculation is relatively fast.
24 /// For exponents above 709, a different algorithm is used.
25 /// Its error bound is not calculated at this point, but probably below 10%.
26 ///
27 /// Returns `e^self`.
28 ///
29 /// # Examples
30 /// ```
31 /// use qfall_math::rational::Q;
32 ///
33 /// let evaluation = Q::from((17, 3)).exp();
34 /// let e = Q::ONE.exp();
35 /// ```
36 ///
37 /// # Panics ...
38 /// - if the exponent is too large (exponent*log_2(e)) >= 2^36.
39 /// This would use up more than 64 GB of RAM.
40 pub fn exp(&self) -> Self {
41 // f64::exp starts mapping to f64::INFINITY between 709.7 and 709.8.
42 if self <= &Q::from(709) {
43 Q::from(f64::from(self).exp())
44 } else {
45 // e^x = 2^(log_2(e^x)) = 2^(x*log_2(e))
46 let log_2_of_e = Q::from(f64::log2(1f64.exp()));
47
48 let base_2_exponent = self * log_2_of_e;
49 let base_2_int_exponent: i64 = (&base_2_exponent.floor()).try_into().unwrap();
50 let base_2_remainder_exponent = base_2_exponent - base_2_int_exponent;
51
52 // Divide x*log_2(e) into an integer part i and the remainder r.
53 // 2^(i+r) = 2^i * 2^r
54 // 2^r ~= r+1 for r in [0, 1]
55 let mut out: Q = base_2_remainder_exponent + 1;
56 unsafe { fmpq_mul_2exp(&mut out.value, &out.value, base_2_int_exponent as u64) }
57 out
58 }
59 }
60
61 /// Computes `e^self` using taylor series approximation of the exponential function.
62 ///
63 /// Parameters:
64 /// - `length_taylor_polynomial`: the length of the taylor series
65 /// approximation of the exponential function
66 ///
67 /// Returns `e^self`.
68 ///
69 /// # Examples
70 /// ```
71 /// use qfall_math::rational::Q;
72 /// use std::str::FromStr;
73 ///
74 /// // sum_{k=0}^999 (17/3)^k/k!
75 /// let evaluation = Q::from((17, 3)).exp_taylor(1000_u32);
76 /// ```
77 pub fn exp_taylor(&self, length_taylor_polynomial: impl Into<u32>) -> Self {
78 let exp_taylor_series = PolyOverQ::exp_function_taylor(length_taylor_polynomial);
79 exp_taylor_series.evaluate(self)
80 }
81}
82
83#[cfg(test)]
84mod test_exp {
85 use super::*;
86 use flint_sys::fmpz::fmpz_get_d_2exp;
87
88 /// Ensure that `e^0 = 1`.
89 #[test]
90 fn zero() {
91 assert_eq!(Q::ONE, Q::ZERO.exp());
92 }
93
94 /// Ensure that `e^1 = e`.
95 #[test]
96 fn one() {
97 let e = Q::ONE.exp();
98
99 assert_eq!(e, Q::from(1f64.exp()));
100 }
101
102 /// Show the exp behavior for large negative inputs zero e^-745, but values smaller than e^-745 do.
103 #[test]
104 fn large_negative_exponent() {
105 let small = Q::from(-745).exp();
106 let small_2 = Q::from((-7451, 10)).exp();
107 let zero = Q::from((-7452, 10)).exp();
108
109 assert_ne!(small, Q::ZERO);
110 assert_eq!(small, small_2);
111 assert_eq!(zero, Q::ZERO);
112 }
113
114 /// Test a large input very close to where exp stops using f64 for calculation.
115 /// It should not saturate f64 into infinity.
116 /// Additionally, the the greater relationship should be correct when transitioning
117 /// to the other algorithm.
118 #[test]
119 fn algorithm_transition_correct() {
120 let result_less_than_709 = (Q::from(709) - Q::from((1, u64::MAX))).exp();
121
122 let result_709 = Q::from(709).exp();
123
124 assert_ne!(f64::from(&result_less_than_709), f64::INFINITY);
125 assert!(result_less_than_709 < result_709);
126 }
127
128 /// Ensure that e^300 is the same when calculated with [`Q`] or [`f64`].
129 #[test]
130 fn medium_exponent() {
131 let a = Q::from(300).exp();
132
133 assert_eq!(a, Q::from(300f64.exp()));
134 }
135
136 /// Ensure that the exponential function for large values outputs different results.
137 #[test]
138 fn large_exponent() {
139 let large_1 = Q::from(u32::MAX).exp();
140 let large_2 = Q::from(u32::MAX - 1).exp();
141
142 assert!(large_1 > large_2);
143 }
144
145 /// Ensure that the exponential function for [`u32::MAX`] has an
146 /// error of at most 6%.
147 #[test]
148 #[allow(clippy::unnecessary_mut_passed)]
149 fn large_exponent_error_bound() {
150 let large = Q::from(u32::MAX).exp();
151
152 // large = e^u32::MAX ~= 1.490856880... × 10^1865280596
153 // <https://www.wolframalpha.com/input?i=1%2Be%5E4294967295>
154
155 // Calculations with these large numbers takes a lot of computing time.
156 // Therefore, we norm them with 2^-6196328016 for further calculations.
157 // 1.490856880 × 10^1865280596 * 2^-6196328016 = 2.42298514666...
158 // 10^1865280596 = 2^6196328016.7006445
159 // value_prime = large * 2^-6196328016
160 let mut exponent = 0;
161 let mut value_prime = unsafe { fmpz_get_d_2exp(&mut exponent, &large.value.num) };
162 value_prime *= 2f64.powi((exponent - 6196328016) as i32);
163
164 // Allow 6% Error (Error Bound chosen based on calculation result)
165 let upper_bound = 1.06 * 2.42298514666;
166 let lower_bound = 0.94 * 2.42298514666;
167
168 assert!(lower_bound < value_prime);
169 assert!(upper_bound > value_prime);
170 }
171}
172
173#[cfg(test)]
174mod test_exp_taylor {
175 use crate::rational::Q;
176
177 /// Ensure that `0` is returned if the length `0` is provided
178 #[test]
179 fn zero_length() {
180 let q = Q::from((17, 3));
181
182 assert_eq!(Q::default(), q.exp_taylor(0_u32));
183 }
184
185 /// Test correct evaluation for some explicit values
186 #[test]
187 fn ten_length_value() {
188 assert_eq!(Q::from((98641, 36288)), Q::ONE.exp_taylor(10_u32));
189 assert_eq!(
190 Q::from((2492063827u64, 1785641760)),
191 Q::from((1, 3)).exp_taylor(10_u32)
192 );
193 assert_eq!(
194 Q::from((5729869, 11160261)),
195 Q::from((-2, 3)).exp_taylor(10_u32)
196 );
197 }
198}