qfall_math/rational/q/arithmetic/logarithm.rs
1// Copyright © 2023 Niklas Siemer
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 logarithm on a [`Q`] integer.
10
11use crate::{error::MathError, integer::Z, rational::Q};
12use flint_sys::fmpz::fmpz_dlog;
13
14impl Q {
15 /// Computes the natural logarithm of a positive rational number
16 /// approximated as an [`f64`] and returned as a [`Q`].
17 ///
18 /// **Warning**: It assumes that the return value does not overflow an [`f64`].
19 ///
20 /// Returns the double precision approximation of the natural logarithm of `self`
21 /// as a [`Q`] instance or a [`MathError`], if `self` is smaller than `1`.
22 ///
23 /// # Examples
24 /// ```
25 /// use qfall_math::rational::Q;
26 ///
27 /// let value = Q::from(1);
28 /// let log = value.ln().unwrap();
29 ///
30 /// assert_eq!(Q::ZERO, log);
31 /// ```
32 ///
33 /// # Errors and Failures
34 /// - Returns a [`MathError`] of type
35 /// [`NonPositive`](MathError::NonPositive) if `self` is not
36 /// greater than `0`.
37 pub fn ln(&self) -> Result<Self, MathError> {
38 if self <= &Q::ZERO {
39 Err(MathError::NonPositive(self.to_string()))
40 } else {
41 Ok(Q::from(
42 unsafe { fmpz_dlog(&self.value.num) } - unsafe { fmpz_dlog(&self.value.den) },
43 ))
44 }
45 }
46
47 /// Computes the logarithm of a positive rational number
48 /// with an integer base greater than `1` approximated as an [`f64`]
49 /// and returned as a [`Q`].
50 ///
51 /// **Warning**: It assumes that the return value does not overflow an [`f64`].
52 ///
53 /// Parameters:
54 /// - `base`: the base of the logarithm
55 ///
56 /// Returns `log_base(self)` as a [`Q`] instance or a [`MathError`],
57 /// if at least one of the conditions `base > 1` and `self > 0` isn't met.
58 ///
59 /// # Examples
60 /// ```
61 /// use qfall_math::rational::Q;
62 ///
63 /// let value = Q::from(2);
64 /// let log = value.log(2).unwrap();
65 ///
66 /// assert_eq!(Q::ONE, log);
67 /// ```
68 ///
69 /// # Errors and Failures
70 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
71 /// if the `base` is smaller than `2`.
72 /// - Returns a [`MathError`] of type
73 /// [`NonPositive`](MathError::NonPositive) if `self` is not
74 /// greater than `0`.
75 pub fn log(&self, base: impl Into<Z>) -> Result<Q, MathError> {
76 let base: Z = base.into();
77 if base <= Z::ONE {
78 return Err(MathError::InvalidIntegerInput(format!(
79 "The base must be greater than 1, but the provided is {base}"
80 )));
81 }
82
83 let ln_value = self.ln()?;
84 let ln_base = base.ln()?;
85 let log_b = ln_value / ln_base;
86
87 Ok(log_b)
88 }
89}
90
91#[cfg(test)]
92mod test_natural_ln {
93 use crate::rational::Q;
94 use std::f64::consts::{LN_2, LN_10};
95
96 /// Ensure that an error is returned if `self` is too small
97 #[test]
98 fn value_too_small() {
99 assert!(Q::ZERO.ln().is_err());
100 assert!(Q::MINUS_ONE.ln().is_err());
101 assert!(Q::from(i64::MIN).ln().is_err());
102 }
103
104 /// Ensure that the output of the function corresponds to the known
105 /// approximated value in [`f64`]
106 #[test]
107 fn static_known_values() {
108 assert_eq!(Q::ZERO, Q::ONE.ln().unwrap());
109 assert_eq!(Q::from(LN_2), Q::from(2).ln().unwrap());
110 assert_eq!(Q::from(LN_10), Q::from(10).ln().unwrap());
111 assert_eq!(Q::ONE, Q::E.ln().unwrap());
112 }
113}
114
115#[cfg(test)]
116mod test_log {
117 use crate::{integer::Z, rational::Q, traits::Distance};
118
119 /// Ensure that an error is returned if the base is too small
120 #[test]
121 fn base_too_small() {
122 let value = Q::from(17);
123
124 assert!(value.log(Z::ZERO).is_err());
125 assert!(value.log(Z::ONE).is_err());
126 assert!(value.log(Z::MINUS_ONE).is_err());
127 assert!(value.log(Z::from(i64::MIN)).is_err());
128 }
129
130 /// Ensure that an error is returned if `self` is too small
131 #[test]
132 fn value_too_small() {
133 let base = Z::from(2);
134
135 assert!(Q::ZERO.log(&base).is_err());
136 assert!(Q::MINUS_ONE.log(&base).is_err());
137 assert!(Q::from(i64::MIN).log(&base).is_err());
138 }
139
140 /// Checks whether the logarithm computation works correctly for small values
141 #[test]
142 fn small_values() {
143 let z_0 = Q::from(1);
144 let z_1 = Q::from(2);
145 let z_2 = Q::from(1.25f64);
146 let z_3 = Q::from(20.75f64);
147 let cmp_0 = Q::from(1.25_f64.log2());
148 let cmp_1 = Q::from(20.75f64.log(3f64));
149 let max_distance = Q::from((1, 1_000_000_000));
150
151 let res_0 = z_0.log(2).unwrap();
152 let res_1 = z_1.log(2).unwrap();
153 let res_2 = z_2.log(2).unwrap();
154 let res_3 = z_3.log(3).unwrap();
155
156 assert_eq!(Q::ZERO, res_0);
157 assert_eq!(Q::ONE, res_1);
158 assert!(cmp_0.distance(res_2) < max_distance);
159 assert!(cmp_1.distance(res_3) < max_distance);
160 }
161
162 /// Checks whether the logarithm computation works correctly for large values
163 #[test]
164 fn large_values() {
165 let z_0 = Q::from(i64::MAX as u64 + 1);
166 let z_1 = Q::from(f64::MAX);
167 let z_2 = Q::from(i32::MAX);
168 let cmp_0 = Q::from((&63, &1));
169 let cmp_1 = Q::from(f64::MAX.log2());
170 let max_distance = Q::from((&1, &1_000_000_000));
171
172 let res_0 = z_0.log(2).unwrap();
173 let res_1 = z_1.log(2).unwrap();
174 let res_2 = z_2.log(i32::MAX).unwrap();
175
176 assert!(cmp_0.distance(res_0) < max_distance);
177 assert!(cmp_1.distance(res_1) < max_distance);
178 assert_eq!(Q::ONE, res_2);
179 }
180
181 /// Ensures that the logarithm function is available for types
182 /// that can be casted to a [`Z`] instance like u8, u16, i32, i64, ...
183 #[test]
184 fn availability() {
185 let value = Z::from(5);
186
187 let _ = value.log(2u8).unwrap();
188 let _ = value.log(2u16).unwrap();
189 let _ = value.log(2u32).unwrap();
190 let _ = value.log(2u64).unwrap();
191 let _ = value.log(2i8).unwrap();
192 let _ = value.log(2i16).unwrap();
193 let _ = value.log(2i32).unwrap();
194 let _ = value.log(2i64).unwrap();
195 let _ = value.log(&value).unwrap();
196 }
197}