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}