qfall_math/integer/z/
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 an integer of type
10//! [`Z`] into a [`String`].
11//!
12//! This includes the [`Display`](std::fmt::Display) trait.
13
14use super::Z;
15use crate::{error::MathError, macros::for_others::implement_for_owned};
16use core::fmt;
17use flint_sys::fmpz::{fmpz_bits, fmpz_get_str, fmpz_tstbit};
18use std::{ffi::CStr, ptr::null_mut, string::FromUtf8Error};
19
20impl From<&Z> for String {
21    /// Converts a [`Z`] into its [`String`] representation.
22    ///
23    /// Parameters:
24    /// - `value`: specifies the integer that will be represented as a [`String`]
25    ///
26    /// Returns a [`String`].
27    ///
28    /// # Examples
29    /// ```
30    /// use qfall_math::integer::Z;
31    /// use std::str::FromStr;
32    /// let z = Z::from_str("6").unwrap();
33    ///
34    /// let string: String = z.into();
35    /// ```
36    fn from(value: &Z) -> Self {
37        value.to_string()
38    }
39}
40
41implement_for_owned!(Z, String, From);
42
43impl fmt::Display for Z {
44    /// Allows to convert an integer of type [`Z`] into a [`String`].
45    ///
46    /// Returns the integer in form of a [`String`]. For integer `1`
47    /// the String looks like this `1`.
48    ///
49    /// # Examples
50    /// ```
51    /// use qfall_math::integer::Z;
52    /// use core::fmt;
53    ///
54    /// let integer = Z::from(42);
55    /// println!("{integer}");
56    /// ```
57    ///
58    /// ```
59    /// use qfall_math::integer::Z;
60    /// use core::fmt;
61    ///
62    /// let integer = Z::from(42);
63    /// let integer_string = integer.to_string();
64    /// ```
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "{}", self.to_string_b(10).unwrap())
67    }
68}
69
70impl Z {
71    /// Allows to convert an integer of type [`Z`] into a [`String`]
72    /// with a configurable base `b` between `2` and `62`.
73    ///
74    /// Parameters:
75    /// - `b`: specifies any base between `2` and `62` which specifies
76    ///   the base of the returned [`String`].
77    ///
78    /// Returns the integer in form of a [`String`] with regards to the base `b`
79    /// or an error, if the base is out of bounds.
80    ///
81    /// # Examples
82    /// ```
83    /// use qfall_math::integer::Z;
84    /// use core::fmt;
85    ///
86    /// let integer = Z::from(42);
87    /// println!("{integer}");
88    /// ```
89    ///
90    /// ```
91    /// use qfall_math::integer::Z;
92    /// use core::fmt;
93    ///
94    /// let integer = Z::from(42);
95    /// let integer_string = integer.to_string();
96    /// ```
97    ///
98    /// # Errors and Failures
99    /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if the
100    ///   base is not between `2` and `62`.
101    pub fn to_string_b(&self, base: i32) -> Result<String, MathError> {
102        if !(2..=62).contains(&base) {
103            return Err(MathError::OutOfBounds(
104                "between 2 and 62".to_owned(),
105                base.to_string(),
106            ));
107        }
108
109        let c_str_ptr = unsafe { fmpz_get_str(null_mut(), base, &self.value) };
110
111        // we expect c_str_ptr to be reference a real value, hence get_str returns an
112        // actual value, hence a simple unwrap should be sufficient and we do not have
113        // to consider an exception
114        //
115        // c_string should not be null either, since we call this method on an
116        // instantiated object
117        let msg = "We expect the pointer to point to a real value and the c_string 
118        not to be null. This error occurs if the provided string does not have UTF-8 format.";
119        let return_str = unsafe { CStr::from_ptr(c_str_ptr).to_str().expect(msg).to_owned() };
120
121        unsafe { libc::free(c_str_ptr as *mut libc::c_void) };
122
123        Ok(return_str)
124    }
125
126    /// Outputs the integer as a [`Vec`] of bytes.
127    /// The inverse function to [`Z::to_bytes`] is [`Z::from_bytes`] for positive numbers including `0`.
128    ///
129    /// **Warning**: The bits are returned as they are stored in the memory. For negative numbers,
130    /// this means that `-1` is output as `[255]`.
131    /// For these values, [`Z::from_bytes`] is not inverse to [`Z::to_bytes`],
132    /// as this function can only instantiate positive values.
133    ///
134    /// Returns a [`Vec<u8>`] of bytes representing the integer as it is stored in memory.
135    ///
136    /// # Examples
137    /// ```
138    /// use qfall_math::integer::Z;
139    ///
140    /// let integer = Z::from(257);
141    ///
142    /// let byte_string = integer.to_bytes();
143    ///
144    /// assert_eq!(vec![1, 1], byte_string);
145    /// ```
146    pub fn to_bytes(&self) -> Vec<u8> {
147        let num_bits = unsafe { fmpz_bits(&self.value) } as usize;
148        let num_bytes = num_bits.div_ceil(8);
149        let mut bytes = vec![0u8; num_bytes];
150
151        for (byte, item) in bytes.iter_mut().enumerate() {
152            for bit_of_byte in (0..8usize).rev() {
153                let bit_index = (byte * u8::BITS as usize + bit_of_byte) as u64;
154                let bit_at_index = unsafe { fmpz_tstbit(&self.value, bit_index) };
155
156                *item *= 2;
157                if bit_at_index == 1 {
158                    *item += 1;
159                }
160            }
161        }
162
163        bytes
164    }
165
166    /// Enables conversion to a UTF8-Encoded [`String`] for [`Z`] values.
167    /// The inverse to this function is [`Z::from_utf8`] for valid UTF8-Encodings.
168    ///
169    /// **Warning**: Not every byte-sequence forms a valid UTF8-character.
170    /// If this is the case, a [`FromUtf8Error`] will be returned.
171    ///
172    /// Returns the corresponding UTF8-encoded [`String`] or a
173    /// [`FromUtf8Error`] if the byte sequence contains an invalid UTF8-character.
174    ///
175    /// # Examples
176    /// ```
177    /// use qfall_math::integer::Z;
178    /// let integer = Z::from(10);
179    ///
180    /// let text: String = integer.to_utf8().unwrap();
181    /// ```
182    ///
183    /// # Errors and Failures
184    /// - Returns a [`FromUtf8Error`] if the integer's byte sequence contains
185    ///   invalid UTF8-characters.
186    pub fn to_utf8(&self) -> Result<String, FromUtf8Error> {
187        String::from_utf8(self.to_bytes())
188    }
189}
190
191#[cfg(test)]
192mod test_to_string {
193    use crate::integer::Z;
194    use std::str::FromStr;
195
196    /// Tests whether a large positive integer works in a roundtrip
197    #[test]
198    fn working_large_positive() {
199        let cmp = Z::from(u64::MAX);
200
201        assert_eq!(u64::MAX.to_string(), cmp.to_string());
202    }
203
204    /// Tests whether a large negative integer works in a roundtrip
205    #[test]
206    fn working_large_negative() {
207        let cmp = Z::from_str(&format!("-{}", u64::MAX)).unwrap();
208
209        assert_eq!(format!("-{}", u64::MAX), cmp.to_string());
210    }
211
212    /// Tests whether a positive integer works in a roundtrip
213    #[test]
214    fn working_positive() {
215        let cmp = Z::from(42);
216
217        assert_eq!("42", cmp.to_string());
218    }
219
220    /// Tests whether a negative integer works in a roundtrip
221    #[test]
222    fn working_negative() {
223        let cmp = Z::from(-42);
224
225        assert_eq!("-42", cmp.to_string());
226    }
227
228    /// Tests whether an integer that is created using a string, returns a
229    /// string that can be used to create a [`Z`]
230    #[test]
231    fn working_use_result_of_to_string_as_input() {
232        let cmp = Z::from(42);
233
234        let cmp_str_2 = cmp.to_string();
235
236        assert!(Z::from_str(&cmp_str_2).is_ok());
237    }
238
239    /// Ensures that the `Into<String>` trait works properly
240    #[test]
241    fn into_works_properly() {
242        let cmp = "6";
243        let integer = Z::from_str(cmp).unwrap();
244
245        let string: String = integer.clone().into();
246        let borrowed_string: String = (&integer).into();
247
248        assert_eq!(cmp, string);
249        assert_eq!(cmp, borrowed_string);
250    }
251}
252
253#[cfg(test)]
254mod test_to_string_b {
255    use crate::integer::Z;
256
257    /// Ensure that an error is returned, if an invalid base is provided
258    #[test]
259    fn out_of_bounds() {
260        let value = Z::from(42);
261
262        assert!(value.to_string_b(-1).is_err());
263        assert!(value.to_string_b(1).is_err());
264        assert!(value.to_string_b(63).is_err());
265    }
266
267    /// Ensure that binary representation works correctly
268    #[test]
269    fn binary() {
270        let value_1 = Z::from(u64::MAX);
271        let cmp_str_1 = "1".repeat(64);
272
273        let value_2 = Z::from(i64::MIN);
274        let cmp_str_2 = format!("-1{}", "0".repeat(63));
275
276        assert_eq!(cmp_str_1, value_1.to_string_b(2).unwrap());
277        assert_eq!(cmp_str_2, value_2.to_string_b(2).unwrap());
278    }
279}
280
281#[cfg(test)]
282mod test_to_bytes {
283    use super::Z;
284
285    /// Ensures that [`Z::to_bytes`] is inverse to [`Z::from_bytes`] for positive values.
286    #[test]
287    fn inverse_to_from_bytes() {
288        let bytes: Vec<u8> = vec![0, 255, 128, 77, 31, 52];
289
290        let integer = Z::from_bytes(&bytes);
291        let cmp_bytes = integer.to_bytes();
292
293        assert_eq!(bytes, cmp_bytes);
294    }
295
296    /// Ensures that [`Z::ZERO`] results in an empty vector of bytes.
297    #[test]
298    fn zero() {
299        let integer = Z::ZERO;
300        let cmp_bytes: Vec<u8> = vec![];
301
302        let bytes = integer.to_bytes();
303
304        assert_eq!(cmp_bytes, bytes);
305    }
306
307    /// Ensure that negative values are represented as they are stored in memory.
308    #[test]
309    fn negative() {
310        let integer = Z::MINUS_ONE;
311        let cmp_bytes: Vec<u8> = vec![255];
312
313        let bytes = integer.to_bytes();
314        let _integer = Z::from_bytes(&bytes);
315
316        assert_eq!(cmp_bytes, bytes);
317    }
318}
319
320#[cfg(test)]
321mod test_to_utf8 {
322    use super::Z;
323
324    /// Ensures that [`Z::to_utf8`] is inverse to [`Z::from_utf8`] for valid UTF8-Encodings.
325    #[test]
326    fn inverse_to_from_utf8() {
327        let cmp_text = "Some valid string formatted in UTF8!";
328
329        let integer = Z::from_utf8(cmp_text);
330        let text = integer.to_utf8().unwrap();
331
332        assert_eq!(cmp_text, text);
333    }
334
335    /// Ensures that [`Z::to_utf8`] outputs an error
336    /// if the integer contains an invalid UTF8-Encoding.
337    #[test]
338    fn invalid_encoding() {
339        let invalid_sequence = [128];
340
341        let integer = Z::from_bytes(&invalid_sequence);
342        let text = integer.to_utf8();
343
344        assert!(text.is_err());
345    }
346}