qfall_math/rational/poly_over_q/
to_string.rs

1// Copyright © 2023 Marcel Luca Schmidt, Marvin Beckmann
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 contains all options to convert a polynomial of type
10//! [`PolyOverQ`] into a [`String`].
11//!
12//! This includes the [`Display`](std::fmt::Display) trait.
13
14use super::PolyOverQ;
15use crate::{macros::for_others::implement_for_owned, traits::GetCoefficient};
16use core::fmt;
17use flint_sys::fmpq_poly::fmpq_poly_get_str;
18use std::ffi::CStr;
19
20impl From<&PolyOverQ> for String {
21    /// Converts a [`PolyOverQ`] into its [`String`] representation.
22    ///
23    /// Parameters:
24    /// - `value`: specifies the polynomial that will be represented as a [`String`]
25    ///
26    /// Returns a [`String`] of the form `"[#number of coefficients]⌴⌴[0th coefficient]⌴[1st coefficient]⌴..."`.
27    ///
28    /// # Examples
29    /// ```
30    /// use qfall_math::rational::PolyOverQ;
31    /// use std::str::FromStr;
32    /// let poly = PolyOverQ::from_str("2  6/7 1").unwrap();
33    ///
34    /// let string: String = poly.into();
35    /// ```
36    fn from(value: &PolyOverQ) -> Self {
37        value.to_string()
38    }
39}
40
41implement_for_owned!(PolyOverQ, String, From);
42
43impl fmt::Display for PolyOverQ {
44    /// Allows to convert a polynomial of type [`PolyOverQ`] into a [`String`].
45    ///
46    /// # Examples
47    /// ```
48    /// use qfall_math::rational::PolyOverQ;
49    /// use std::str::FromStr;
50    /// use core::fmt;
51    ///
52    /// let poly = PolyOverQ::from_str("5  0 1 2/5 -3/2 1").unwrap();
53    /// println!("{poly}");
54    /// ```
55    ///
56    /// ```
57    /// use qfall_math::rational::PolyOverQ;
58    /// use std::str::FromStr;
59    ///
60    /// let poly = PolyOverQ::from_str("5  0 1 2/5 -3/2 1").unwrap();
61    /// let poly_string = poly.to_string();
62    /// ```
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let c_str_ptr = unsafe { fmpq_poly_get_str(&self.poly) };
65        let return_str = unsafe { CStr::from_ptr(c_str_ptr).to_str().unwrap().to_owned() };
66        // free the space allocated by the pointer
67        unsafe { libc::free(c_str_ptr as *mut libc::c_void) };
68        write!(f, "{return_str}")
69    }
70}
71
72impl PolyOverQ {
73    /// Outputs a representation of [`PolyOverQ`] with the decimal representation
74    /// of each coefficient with the specified number of decimal digits.
75    /// If a coefficient can't be represented exactly, it provides the
76    /// closest value representable with `nr_decimal_digits` rounded towards zero.
77    ///
78    /// **WARNING:** This function converts every coefficient into an [`f64`] before
79    /// outputting the decimal representation. Thus, values that can't be represented exactly
80    /// by a [`f64`] will lose some precision. For large values, e.g. of size `2^64`
81    /// the deviation to the original value might be within the size of `1_000`.
82    ///
83    /// Parameters:
84    /// - `nr_decimal_digits`: specifies the number of decimal digits
85    ///   that will be a part of the output [`String`]
86    ///
87    /// Returns the polynomial in form of a [`String`]. For polynomial `2  1/2 5/3`
88    /// the [`String`] looks like this `2  0.50 1.66` if `nr_decimal_digits = 2`.
89    ///
90    /// # Examples
91    /// ```
92    /// use qfall_math::rational::PolyOverQ;
93    /// use std::str::FromStr;
94    /// let poly = PolyOverQ::from_str("4  5/2 2 -2/3 4/3").unwrap();
95    ///
96    /// let decimal_repr = poly.to_string_decimal(3);
97    /// ```
98    pub fn to_string_decimal(&self, nr_decimal_digits: usize) -> String {
99        let degree = self.get_degree() + 1;
100        let mut poly_string = format!("{degree}  ");
101
102        for i in 0..degree {
103            // swap with get_coeff_unchecked once available
104            let entry = unsafe { self.get_coeff_unchecked(i) };
105            let entry_string = entry.to_string_decimal(nr_decimal_digits);
106
107            poly_string.push_str(&entry_string);
108            poly_string.push(' ');
109        }
110        poly_string = poly_string.trim().to_string();
111
112        poly_string
113    }
114}
115
116/// This module avoids tests already performed for [`crate::rational::Q::to_string_decimal`].
117#[cfg(test)]
118mod test_to_string_decimal {
119    use super::PolyOverQ;
120    use std::str::FromStr;
121
122    /// Ensures that [`PolyOverQ::to_string_decimal`] works for different degrees
123    /// and different `nr_decimal_digits`.
124    #[test]
125    fn different_degrees() {
126        let a = PolyOverQ::from_str("0").unwrap();
127        let b = PolyOverQ::from_str("1  1/3").unwrap();
128        let c = PolyOverQ::from_str("3  1/3 0 -5/3").unwrap();
129
130        let a_0 = a.to_string_decimal(0);
131        let a_1 = a.to_string_decimal(1);
132        let b_0 = b.to_string_decimal(0);
133        let b_2 = b.to_string_decimal(2);
134        let c_0 = c.to_string_decimal(0);
135        let c_1 = c.to_string_decimal(1);
136
137        assert_eq!("0", a_0);
138        assert_eq!("0", a_1);
139        assert_eq!("1  0", b_0);
140        assert_eq!("1  0.33", b_2);
141        assert_eq!("3  0 0 -2", c_0);
142        assert_eq!("3  0.3 0.0 -1.7", c_1);
143    }
144
145    /// Ensures that [`PolyOverQ::to_string_decimal`] does not panic if it is given a
146    /// value it can not fully represent.
147    #[test]
148    fn panic_precision() {
149        let a = PolyOverQ::from_str(&format!("1  {}", u64::MAX)).unwrap();
150
151        let _ = a.to_string_decimal(1);
152    }
153}
154
155#[cfg(test)]
156mod test_to_string {
157    use super::PolyOverQ;
158    use std::str::FromStr;
159
160    /// Tests whether a polynomial that is created using a string, returns the
161    /// same string, when it is converted back to a string
162    #[test]
163    fn working_keeps_same_string() {
164        let cmp_str = "5  0 1 2/5 -3/2 1";
165        let cmp = PolyOverQ::from_str(cmp_str).unwrap();
166
167        assert_eq!(cmp_str, cmp.to_string());
168    }
169
170    /// Tests whether a polynomial that is created using a string, returns a
171    /// string that can be used to create a polynomial
172    #[test]
173    fn working_use_result_of_to_string_as_input() {
174        let cmp_str = "5  0 1 2/5 -3/2 1";
175        let cmp = PolyOverQ::from_str(cmp_str).unwrap();
176
177        let cmp_str_2 = cmp.to_string();
178
179        assert!(PolyOverQ::from_str(&cmp_str_2).is_ok());
180    }
181
182    /// Ensures that the `Into<String>` trait works properly
183    #[test]
184    fn into_works_properly() {
185        let cmp = "2  6/7 2";
186        let poly = PolyOverQ::from_str(cmp).unwrap();
187
188        let string: String = poly.clone().into();
189        let borrowed_string: String = (&poly).into();
190
191        assert_eq!(cmp, string);
192        assert_eq!(cmp, borrowed_string);
193    }
194}