qfall_math/rational/q/arithmetic/
pow.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//! This module provides an implementation of the [`Pow`] trait for [`Q`].
10
11use crate::{error::MathError, integer::Z, rational::Q, traits::Pow};
12use flint_sys::fmpq::fmpq_pow_fmpz;
13
14impl<Integer: Into<Z>> Pow<Integer> for Q {
15    type Output = Q;
16
17    /// Raises the value of `self` to the power of an integer `exp`.
18    ///
19    /// Parameters:
20    /// - `exp`: specifies the exponent to which the value is raised
21    ///
22    /// Returns the value of `self` powered by `exp` as a new [`Q`] instance
23    /// or a [`MathError`], if the exponent is negative and the base value of `self` is not invertible.
24    ///
25    /// # Examples
26    /// ```
27    /// use qfall_math::{rational::Q, integer::Z};
28    /// use qfall_math::traits::*;
29    ///
30    /// let base = Q::from(3);
31    ///
32    /// let powered_value = base.pow(-2).unwrap();
33    ///
34    /// assert_eq!(Q::from((1, 9)), powered_value);
35    /// ```
36    ///
37    /// # Errors and Failures
38    /// - Returns a [`MathError`] of type [`InvalidExponent`](MathError::InvalidExponent)
39    ///   if the provided exponent is negative and the base value of `self` is not invertible.
40    fn pow(&self, exp: Integer) -> Result<Self::Output, MathError> {
41        let exp = exp.into();
42        if self == &Q::ZERO && exp < Z::ZERO {
43            return Err(MathError::InvalidExponent(format!(
44                "A negative exponent {exp} was used for a zero value. There's no inverse for zero values."
45            )));
46        }
47
48        let mut out = Q::ZERO;
49        unsafe { fmpq_pow_fmpz(&mut out.value, &self.value, &exp.value) };
50        Ok(out)
51    }
52}
53
54#[cfg(test)]
55mod test_pow {
56    use super::*;
57
58    /// Ensure that `pow` works correctly for zero values
59    #[test]
60    fn zero() {
61        let zero = Q::ZERO;
62
63        assert_eq!(Q::ONE, zero.pow(0).unwrap());
64        assert_eq!(Q::ZERO, zero.pow(1).unwrap());
65        assert!(zero.pow(-1).is_err());
66    }
67
68    /// Ensure that `pow` works correctly for base values `1` and `-1`
69    #[test]
70    fn one() {
71        let base_pos = Q::ONE;
72        let base_neg = Q::MINUS_ONE;
73
74        assert_eq!(Q::ONE, base_pos.pow(0).unwrap());
75        assert_eq!(Q::ONE, base_pos.pow(1).unwrap());
76        assert_eq!(Q::ONE, base_pos.pow(-2).unwrap());
77        assert_eq!(Q::ONE, base_pos.pow(5).unwrap());
78        assert_eq!(Q::ONE, base_neg.pow(0).unwrap());
79        assert_eq!(Q::MINUS_ONE, base_neg.pow(1).unwrap());
80        assert_eq!(Q::ONE, base_neg.pow(-2).unwrap());
81        assert_eq!(Q::MINUS_ONE, base_neg.pow(5).unwrap());
82    }
83
84    /// Ensure that `pow` works for [`Q`] properly for small values
85    #[test]
86    fn small() {
87        let base_0 = Q::from(2);
88        let base_1 = Q::from((1, 2));
89        let exp_pos = Z::from(4);
90
91        let res_0 = base_0.pow(&exp_pos).unwrap();
92        let res_1 = base_0.pow(0).unwrap();
93        let res_2 = base_1.pow(&exp_pos).unwrap();
94        let res_3 = base_1.pow(0).unwrap();
95
96        assert_eq!(Q::from(16), res_0);
97        assert_eq!(Q::ONE, res_1);
98        assert_eq!(Q::from((1, 16)), res_2);
99        assert_eq!(Q::ONE, res_3);
100    }
101
102    /// Ensure that `pow` works for [`Q`] properly for large values
103    #[test]
104    fn large() {
105        let base_0 = Q::from(i64::MIN);
106        let base_1 = Q::from((1, i64::MIN));
107        let exp_pos = Z::from(3);
108        let cmp_0 = &base_0 * &base_0 * &base_0;
109        let cmp_1 = &base_1 * &base_1 * &base_1;
110
111        let res_0 = base_0.pow(&exp_pos).unwrap();
112        let res_1 = base_0.pow(0).unwrap();
113        let res_2 = base_0.pow(-1).unwrap();
114        let res_3 = base_1.pow(&exp_pos).unwrap();
115        let res_4 = base_1.pow(0).unwrap();
116        let res_5 = base_1.pow(-1).unwrap();
117
118        assert_eq!(cmp_0, res_0);
119        assert_eq!(Q::ONE, res_1);
120        assert_eq!(Q::from((1, i64::MIN)), res_2);
121        assert_eq!(cmp_1, res_3);
122        assert_eq!(Q::ONE, res_4);
123        assert_eq!(Q::from(i64::MIN), res_5);
124    }
125
126    /// Ensures that the `pow` trait is available for other types
127    #[test]
128    fn availability() {
129        let base = Q::from(i64::MAX);
130        let exp = Z::from(4);
131
132        let _ = base.pow(exp);
133        let _ = base.pow(2_i8);
134        let _ = base.pow(2_i16);
135        let _ = base.pow(2_i32);
136        let _ = base.pow(2_i64);
137        let _ = base.pow(2_u8);
138        let _ = base.pow(2_u16);
139        let _ = base.pow(2_u32);
140        let _ = base.pow(2_u64);
141    }
142
143    /// Ensures that `pow` returns an error if a non-invertible basis,
144    /// i.e. for [`Q`] only 0, is powered by a negative exponent
145    #[test]
146    fn non_invertible_detection() {
147        let base = Q::ZERO;
148
149        assert!(base.pow(-1).is_err());
150    }
151}