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}