qfall_math/rational/mat_q/
to_string.rs

1// Copyright © 2023 Marcel Luca Schmidt
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 matrix of type
10//! [`MatQ`] into a [`String`].
11//!
12//! This includes the [`Display`](std::fmt::Display) trait.
13
14use super::MatQ;
15use crate::{
16    macros::for_others::implement_for_owned,
17    traits::{MatrixDimensions, MatrixGetEntry},
18    utils::parse::matrix_to_string,
19};
20use core::fmt;
21
22impl From<&MatQ> for String {
23    /// Converts a [`MatQ`] into its [`String`] representation.
24    ///
25    /// Parameters:
26    /// - `value`: specifies the matrix that will be represented as a [`String`]
27    ///
28    /// Returns a [`String`] of the form `"[[row_0],[row_1],...[row_n]]"`.
29    ///
30    /// # Examples
31    /// ```
32    /// use qfall_math::rational::MatQ;
33    /// use std::str::FromStr;
34    /// let matrix = MatQ::from_str("[[6/7, 1],[5, 2/3]]").unwrap();
35    ///
36    /// let string: String = matrix.into();
37    /// ```
38    fn from(value: &MatQ) -> Self {
39        value.to_string()
40    }
41}
42
43implement_for_owned!(MatQ, String, From);
44crate::macros::serialize::matrix_pretty_string!(MatQ, Q);
45
46impl fmt::Display for MatQ {
47    /// Allows to convert a matrix of type [`MatQ`] into a [`String`].
48    ///
49    /// Returns the Matrix in form of a [`String`]. For matrix `[[1/2, 2, 3/4],[4, 5/3, 6/2]]`
50    /// the String looks like this `[[1/2, 2, 3/4],[4, 5/3, 3]]`.
51    ///
52    /// # Examples
53    /// ```
54    /// use qfall_math::rational::MatQ;
55    /// use core::fmt;
56    /// use std::str::FromStr;
57    ///
58    /// let matrix = MatQ::from_str("[[1/2, 2, 3/4],[4, 5/3, 6]]").unwrap();
59    /// println!("{matrix}");
60    /// ```
61    ///
62    /// ```
63    /// use qfall_math::rational::MatQ;
64    /// use core::fmt;
65    /// use std::str::FromStr;
66    ///
67    /// let matrix = MatQ::from_str("[[1/2, 2, 3/4],[4, 5/3, 6]]").unwrap();
68    /// let matrix_string = matrix.to_string();
69    /// ```
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "{}", matrix_to_string(self))
72    }
73}
74
75impl MatQ {
76    /// Outputs a representation of [`MatQ`] with the decimal representation of each entry
77    /// with the specified number of decimal digits.
78    /// If an entry can't be represented exactly, it provides the
79    /// closest value representable with `nr_decimal_digits` rounded towards zero.
80    ///
81    /// **WARNING:** This function converts every entry into an [`f64`] before
82    /// outputting the decimal representation. Thus, values that can't be represented exactly
83    /// by a [`f64`] will lose some precision. For large values, e.g. of size `2^64`
84    /// the deviation to the original value might be within the size of `1_000`.
85    ///
86    /// Parameters:
87    /// - `nr_decimal_digits`: specifies the number of decimal digits
88    ///   that will be a part of the output [`String`]
89    ///
90    /// Returns the matrix in form of a [`String`]. For matrix `[[1/2],[5/3]]`
91    /// the [`String`] looks like this `[[0.50],[1.66]]` if `nr_decimal_digits = 2`.
92    ///
93    /// # Examples
94    /// ```
95    /// use qfall_math::rational::MatQ;
96    /// use std::str::FromStr;
97    /// let matrix = MatQ::from_str("[[5/2, 2],[-2/3, 4/3]]").unwrap();
98    ///
99    /// let decimal_repr = matrix.to_string_decimal(3);
100    /// ```
101    pub fn to_string_decimal(&self, nr_decimal_digits: usize) -> String {
102        let mut matrix_string = String::from("[");
103        let nr_rows = self.get_num_rows() - 1;
104        let nr_cols = self.get_num_columns() - 1;
105
106        for row in 0..nr_rows {
107            matrix_string.push('[');
108            for column in 0..nr_cols {
109                self.get_entry_and_push_str(&mut matrix_string, row, column, nr_decimal_digits);
110                matrix_string.push_str(", ");
111            }
112            self.get_entry_and_push_str(&mut matrix_string, row, nr_cols, nr_decimal_digits);
113            matrix_string.push_str("],\n");
114        }
115        matrix_string.push('[');
116        for column in 0..nr_cols {
117            self.get_entry_and_push_str(&mut matrix_string, nr_rows, column, nr_decimal_digits);
118            matrix_string.push_str(", ");
119        }
120        self.get_entry_and_push_str(&mut matrix_string, nr_rows, nr_cols, nr_decimal_digits);
121        matrix_string.push_str("]]");
122
123        matrix_string
124    }
125
126    /// Helper function to assemble a [`String`] for a matrix easily.
127    ///
128    /// Parameters:
129    /// - `matrix_string`: [`String`] to append the decimal representation of the matrices entry
130    /// - `row`: specifies the row, in which the desired entry is located
131    /// - `column`: specifies the column, in which the desired entry is located
132    /// - `nr_decimal_digits`: specifies how many digits the decimal representation of the entry should have
133    ///
134    /// # Examples
135    /// ```compile_fail
136    /// use qfall_math::rational::MatQ;
137    /// use std::str::FromStr;
138    /// let matrix = MatQ::from_str("[[5/2, 2],[-2/3, 4/3]]").unwrap();
139    /// let mut matrix_string = String::from("[[");
140    ///
141    /// matrix.get_entry_and_push_str(&mut matrix_string, 0, 0, 2);
142    ///
143    /// assert_eq!("[[2.50", matrix_string);
144    /// ```
145    fn get_entry_and_push_str(
146        &self,
147        matrix_string: &mut String,
148        row: i64,
149        column: i64,
150        nr_decimal_digits: usize,
151    ) {
152        let entry = unsafe { self.get_entry_unchecked(row, column) };
153        let entry_string = entry.to_string_decimal(nr_decimal_digits);
154        matrix_string.push_str(&entry_string);
155    }
156}
157
158/// This module avoids tests already performed for [`crate::rational::Q::to_string_decimal`].
159#[cfg(test)]
160mod test_to_string_decimal {
161    use super::MatQ;
162    use std::str::FromStr;
163
164    /// Ensures that [`MatQ::to_string_decimal`] works for matrices of different dimensions.
165    #[test]
166    fn dimensions() {
167        let a = MatQ::from_str("[[3/2, 1/2],[-1, -7/3]]").unwrap();
168        let b = MatQ::from_str("[[3/2],[-7/3]]").unwrap();
169        let c = MatQ::from_str("[[3/2, 1/2, -7/3]]").unwrap();
170
171        let a_0 = a.to_string_decimal(0);
172        let a_1 = a.to_string_decimal(1);
173        let b_0 = b.to_string_decimal(0);
174        let b_2 = b.to_string_decimal(2);
175        let c_0 = c.to_string_decimal(0);
176        let c_1 = c.to_string_decimal(1);
177
178        assert_eq!("[[2, 0],\n[-1, -2]]", a_0);
179        assert_eq!("[[1.5, 0.5],\n[-1.0, -2.3]]", a_1);
180        assert_eq!("[[2],\n[-2]]", b_0);
181        assert_eq!("[[1.50],\n[-2.33]]", b_2);
182        assert_eq!("[[2, 0, -2]]", c_0);
183        assert_eq!("[[1.5, 0.5, -2.3]]", c_1);
184    }
185}
186
187#[cfg(test)]
188mod test_to_string {
189    use crate::rational::MatQ;
190    use std::str::FromStr;
191
192    /// Tests whether a matrix with large nominators and denominators works in a
193    /// roundtrip
194    #[test]
195    fn working_large_positive() {
196        let cmp =
197            MatQ::from_str(&format!("[[{}, 1/{}, 3],[5, 6, 7]]", u64::MAX, u64::MAX)).unwrap();
198
199        assert_eq!(
200            format!("[[{}, 1/{}, 3],[5, 6, 7]]", u64::MAX, u64::MAX),
201            cmp.to_string()
202        )
203    }
204
205    /// Tests whether a matrix with large negative nominators and denominators
206    /// works in a roundtrip
207    #[test]
208    fn working_large_negative() {
209        let cmp =
210            MatQ::from_str(&format!("[[-{}, 1/-{}, 3],[5, 6, 7]]", u64::MAX, u64::MAX)).unwrap();
211
212        assert_eq!(
213            format!("[[-{}, -1/{}, 3],[5, 6, 7]]", u64::MAX, u64::MAX),
214            cmp.to_string()
215        )
216    }
217
218    /// Tests whether a matrix with positive nominators and denominators works
219    /// in a roundtrip
220    #[test]
221    fn working_positive() {
222        let cmp = MatQ::from_str("[[2, 1, 2/3],[5, 6, 14/7]]").unwrap();
223
224        assert_eq!("[[2, 1, 2/3],[5, 6, 2]]", cmp.to_string());
225    }
226
227    /// Tests whether a matrix with negative nominators and denominators works
228    /// in a roundtrip
229    #[test]
230    fn working_negative() {
231        let cmp = MatQ::from_str("[[-2, 1, -3/8],[5, 1/-6, -14/7]]").unwrap();
232
233        assert_eq!("[[-2, 1, -3/8],[5, -1/6, -2]]", cmp.to_string());
234    }
235
236    /// Tests whether a large matrix works in a roundtrip
237    #[test]
238    fn working_large_dimensions() {
239        let cmp_1 = MatQ::from_str(&format!("[{}[5, 6, 7]]", "[1/2, 2, 3/8],".repeat(99))).unwrap();
240        let cmp_2 = MatQ::from_str(&format!("[[{}1]]", "1/4, ".repeat(99))).unwrap();
241
242        assert_eq!(
243            format!("[{}[5, 6, 7]]", "[1/2, 2, 3/8],".repeat(99)),
244            cmp_1.to_string()
245        );
246        assert_eq!(format!("[[{}1]]", "1/4, ".repeat(99)), cmp_2.to_string());
247    }
248
249    /// Tests whether a matrix that is created using a string, returns a
250    /// string that can be used to create a [`MatQ`]
251    #[test]
252    fn working_use_result_of_to_string_as_input() {
253        let cmp = MatQ::from_str("[[-2/5, 1, 3],[5, 1/-6, 14/7]]").unwrap();
254
255        let cmp_str_2 = cmp.to_string();
256
257        assert!(MatQ::from_str(&cmp_str_2).is_ok());
258    }
259
260    /// Ensures that the `Into<String>` trait works properly
261    #[test]
262    fn into_works_properly() {
263        let cmp = "[[6/7, 1, 3],[5, 2/3, 7]]";
264        let matrix = MatQ::from_str(cmp).unwrap();
265
266        let string: String = matrix.clone().into();
267        let borrowed_string: String = (&matrix).into();
268
269        assert_eq!(cmp, string);
270        assert_eq!(cmp, borrowed_string);
271    }
272}