malachite_q/conversion/string/
from_sci_string.rs

1// Copyright © 2025 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::Rational;
10use crate::arithmetic::traits::SimplestRationalInInterval;
11use malachite_base::num::arithmetic::traits::Pow;
12use malachite_base::num::conversion::string::from_sci_string::preprocess_sci_string;
13use malachite_base::num::conversion::string::options::FromSciStringOptions;
14use malachite_base::num::conversion::traits::FromSciString;
15use malachite_nz::integer::Integer;
16use malachite_nz::natural::conversion::string::from_sci_string::FromSciStringHelper;
17
18impl FromSciString for Rational {
19    /// Converts a string, possibly in scientfic notation, to a [`Rational`].
20    ///
21    /// Use [`FromSciStringOptions`] to specify the base (from 2 to 36, inclusive). The rounding
22    /// mode option is ignored.
23    ///
24    /// If the base is greater than 10, the higher digits are represented by the letters `'a'`
25    /// through `'z'` or `'A'` through `'Z'`; the case doesn't matter and doesn't need to be
26    /// consistent.
27    ///
28    /// Exponents are allowed, and are indicated using the character `'e'` or `'E'`. If the base is
29    /// 15 or greater, an ambiguity arises where it may not be clear whether `'e'` is a digit or an
30    /// exponent indicator. To resolve this ambiguity, always use a `'+'` or `'-'` sign after the
31    /// exponent indicator when the base is 15 or greater.
32    ///
33    /// The exponent itself is always parsed using base 10.
34    ///
35    /// Decimal (or other-base) points are allowed.
36    ///
37    /// If the string is unparseable, `None` is returned.
38    ///
39    /// This function is very literal; given `"0.333"`, it will return $333/1000$ rather than $1/3$.
40    /// If you'd prefer that it return $1/3$, consider using
41    /// [`from_sci_string_simplest`](Rational::from_sci_string_simplest) instead. However, that
42    /// function has its quirks too: given `"0.1"`, it will not return $1/10$ (see its documentation
43    /// for an explanation of this behavior). This function _does_ return $1/10$.
44    ///
45    /// # Worst-case complexity
46    /// $T(n, m) = O(m^n n \log m (\log n + \log\log m))$
47    ///
48    /// $M(n, m) = O(m^n n \log m)$
49    ///
50    /// where $T$ is time, $M$ is additional memory, $n$ is `s.len()`, and $m$ is `options.base`.
51    ///
52    /// # Examples
53    /// ```
54    /// use malachite_base::num::conversion::string::options::FromSciStringOptions;
55    /// use malachite_base::num::conversion::traits::FromSciString;
56    /// use malachite_q::Rational;
57    ///
58    /// assert_eq!(Rational::from_sci_string("123").unwrap(), 123);
59    /// assert_eq!(
60    ///     Rational::from_sci_string("0.1").unwrap().to_string(),
61    ///     "1/10"
62    /// );
63    /// assert_eq!(
64    ///     Rational::from_sci_string("0.10").unwrap().to_string(),
65    ///     "1/10"
66    /// );
67    /// assert_eq!(
68    ///     Rational::from_sci_string("0.333").unwrap().to_string(),
69    ///     "333/1000"
70    /// );
71    /// assert_eq!(Rational::from_sci_string("1.2e5").unwrap(), 120000);
72    /// assert_eq!(
73    ///     Rational::from_sci_string("1.2e-5").unwrap().to_string(),
74    ///     "3/250000"
75    /// );
76    ///
77    /// let mut options = FromSciStringOptions::default();
78    /// options.set_base(16);
79    /// assert_eq!(
80    ///     Rational::from_sci_string_with_options("ff", options).unwrap(),
81    ///     255
82    /// );
83    /// assert_eq!(
84    ///     Rational::from_sci_string_with_options("ffE+5", options).unwrap(),
85    ///     267386880
86    /// );
87    /// assert_eq!(
88    ///     Rational::from_sci_string_with_options("ffE-5", options)
89    ///         .unwrap()
90    ///         .to_string(),
91    ///     "255/1048576"
92    /// );
93    /// ```
94    fn from_sci_string_with_options(s: &str, options: FromSciStringOptions) -> Option<Self> {
95        let (s, exponent) = preprocess_sci_string(s, options)?;
96        let x = Self::from(Integer::parse_int(&s, options.get_base())?);
97        Some(x * Self::from(options.get_base()).pow(exponent))
98    }
99}
100
101impl Rational {
102    /// Converts a string, possibly in scientfic notation, to a [`Rational`]. This function finds
103    /// the simplest [`Rational`] which rounds to the target string according to the precision
104    /// implied by the string.
105    ///
106    /// Use [`FromSciStringOptions`] to specify the base (from 2 to 36, inclusive). The rounding
107    /// mode option is ignored.
108    ///
109    /// If the base is greater than 10, the higher digits are represented by the letters `'a'`
110    /// through `'z'` or `'A'` through `'Z'`; the case doesn't matter and doesn't need to be
111    /// consistent.
112    ///
113    /// Exponents are allowed, and are indicated using the character `'e'` or `'E'`. If the base is
114    /// 15 or greater, an ambiguity arises where it may not be clear whether `'e'` is a digit or an
115    /// exponent indicator. To resolve this ambiguity, always use a `'+'` or `'-'` sign after the
116    /// exponent indicator when the base is 15 or greater.
117    ///
118    /// The exponent itself is always parsed using base 10.
119    ///
120    /// Decimal (or other-base) points are allowed.
121    ///
122    /// If the string is unparseable, `None` is returned.
123    ///
124    /// Here's a more precise description of the function's behavior. Suppose we are using base $b$,
125    /// and the literal value of the string (as parsed by
126    /// [`from_sci_string`](Rational::from_sci_string)) is $q$, and the implied scale is $s$
127    /// (meaning $s$ digits are provided after the point; if the string is `"123.456"`, then $s$ is
128    /// 3). Then this function computes $\varepsilon = b^{-s}/2$ and finds the simplest [`Rational`]
129    /// in the closed interval $[q - \varepsilon, q + \varepsilon]$. The simplest [`Rational`] is
130    /// the one with minimal denominator; if there are multiple such [`Rational`]s, the one with the
131    /// smallest absolute numerator is chosen.
132    ///
133    /// The following discussion assumes base 10.
134    ///
135    /// This method allows the function to convert `"0.333"` to $1/3$, since $1/3$ is the simplest
136    /// [`Rational`] in the interval $[0.3325, 0.3335]$. But note that if the scale of the input is
137    /// low, some unexpected behavior may occur. For example, `"0.1"` will be converted into $1/7$
138    /// rather than $1/10$, since $1/7$ is the simplest [`Rational`] in $[0.05, 0.15]$. If you'd
139    /// prefer that result be $1/10$, you have a few options:
140    /// - Use [`from_sci_string_with_options`](Rational::from_sci_string_with_options) instead. This
141    ///   function interprets its input literally; it converts `"0.333"` to $333/1000$.
142    /// - Increase the scale of the input; `"0.10"` is converted to $1/10$.
143    /// - Use [`from_sci_string_with_options`](Rational::from_sci_string_with_options), and round
144    ///   the result manually using functions like
145    ///   [`round_to_multiple`](malachite_base::num::arithmetic::traits::RoundToMultiple::round_to_multiple)
146    ///   and
147    ///   [`simplest_rational_in_closed_interval`](Rational::simplest_rational_in_closed_interval).
148    ///
149    /// # Worst-case complexity
150    /// $T(n, m) = O(m^n n \log m (\log n + \log\log m))$
151    ///
152    /// $M(n, m) = O(m^n n \log m)$
153    ///
154    /// where $T$ is time, $M$ is additional memory, $n$ is `s.len()`, and $m$ is `options.base`.
155    ///
156    /// # Examples
157    /// ```
158    /// use malachite_base::num::conversion::string::options::FromSciStringOptions;
159    /// use malachite_q::Rational;
160    ///
161    /// let mut options = FromSciStringOptions::default();
162    /// options.set_base(16);
163    /// assert_eq!(
164    ///     Rational::from_sci_string_simplest_with_options("ff", options).unwrap(),
165    ///     255
166    /// );
167    /// assert_eq!(
168    ///     Rational::from_sci_string_simplest_with_options("ffE+5", options).unwrap(),
169    ///     267386880
170    /// );
171    /// // 1/4105 is 0.000ff705..._16
172    /// assert_eq!(
173    ///     Rational::from_sci_string_simplest_with_options("ffE-5", options)
174    ///         .unwrap()
175    ///         .to_string(),
176    ///     "1/4105"
177    /// );
178    /// ```
179    pub fn from_sci_string_simplest_with_options(
180        s: &str,
181        options: FromSciStringOptions,
182    ) -> Option<Self> {
183        let (s, exponent) = preprocess_sci_string(s, options)?;
184        let x = Self::from(Integer::parse_int(&s, options.get_base())?);
185        let p = Self::from(options.get_base()).pow(exponent);
186        let q = x * &p;
187        if exponent >= 0 {
188            Some(q)
189        } else {
190            let epsilon = p >> 1;
191            Some(Self::simplest_rational_in_closed_interval(
192                &(&q - &epsilon),
193                &(q + epsilon),
194            ))
195        }
196    }
197
198    /// Converts a string, possibly in scientfic notation, to a [`Rational`]. This function finds
199    /// the simplest [`Rational`] which rounds to the target string according to the precision
200    /// implied by the string.
201    ///
202    /// The string is parsed using base 10. To use other bases, try
203    /// [`from_sci_string_simplest_with_options`](Rational::from_sci_string_simplest_with_options)
204    /// instead.
205    ///
206    /// Exponents are allowed, and are indicated using the character `'e'` or `'E'`.
207    ///
208    /// The exponent itself is also parsed using base 10.
209    ///
210    /// Decimal points are allowed.
211    ///
212    /// If the string is unparseable, `None` is returned.
213    ///
214    /// Here's a more precise description of the function's behavior. Suppose that the literal value
215    /// of the string (as parsed by [`from_sci_string`](Rational::from_sci_string)) is $q$, and the
216    /// implied scale is $s$ (meaning $s$ digits are provided after the point; if the string is
217    /// `"123.456"`, then $s$ is 3). Then this function computes $\varepsilon = 10^{-s}/2$ and finds
218    /// the simplest [`Rational`] in the closed interval $[q - \varepsilon, q + \varepsilon]$. The
219    /// simplest [`Rational`] is the one with minimal denominator; if there are multiple such
220    /// [`Rational`]s, the one with the smallest absolute numerator is chosen.
221    ///
222    /// This method allows the function to convert `"0.333"` to $1/3$, since $1/3$ is the simplest
223    /// [`Rational`] in the interval $[0.3325, 0.3335]$. But note that if the scale of the input is
224    /// low, some unexpected behavior may occur. For example, `"0.1"` will be converted into $1/7$
225    /// rather than $1/10$, since $1/7$ is the simplest [`Rational`] in $[0.05, 0.15]$. If you'd
226    /// prefer that result be $1/10$, you have a few options:
227    /// - Use [`from_sci_string`](Rational::from_sci_string) instead. This function interprets its
228    ///   input literally; it converts `"0.333"` to $333/1000$.
229    /// - Increase the scale of the input; `"0.10"` is converted to $1/10$.
230    /// - Use [`from_sci_string`](Rational::from_sci_string), and round the result manually using
231    ///   functions like
232    ///   [`round_to_multiple`](malachite_base::num::arithmetic::traits::RoundToMultiple::round_to_multiple)
233    ///   and
234    ///   [`simplest_rational_in_closed_interval`](Rational::simplest_rational_in_closed_interval).
235    ///
236    /// # Worst-case complexity
237    /// $T(n) = O(10^n n \log n)$
238    ///
239    /// $M(n) = O(10^n n)$
240    ///
241    /// where $T$ is time, $M$ is additional memory, and $n$ is `s.len()`.
242    ///
243    /// # Examples
244    /// ```
245    /// use malachite_q::Rational;
246    ///
247    /// assert_eq!(Rational::from_sci_string_simplest("123").unwrap(), 123);
248    /// assert_eq!(
249    ///     Rational::from_sci_string_simplest("0.1")
250    ///         .unwrap()
251    ///         .to_string(),
252    ///     "1/7"
253    /// );
254    /// assert_eq!(
255    ///     Rational::from_sci_string_simplest("0.10")
256    ///         .unwrap()
257    ///         .to_string(),
258    ///     "1/10"
259    /// );
260    /// assert_eq!(
261    ///     Rational::from_sci_string_simplest("0.333")
262    ///         .unwrap()
263    ///         .to_string(),
264    ///     "1/3"
265    /// );
266    /// assert_eq!(Rational::from_sci_string_simplest("1.2e5").unwrap(), 120000);
267    /// assert_eq!(
268    ///     Rational::from_sci_string_simplest("1.2e-5")
269    ///         .unwrap()
270    ///         .to_string(),
271    ///     "1/80000"
272    /// );
273    /// ```
274    #[inline]
275    pub fn from_sci_string_simplest(s: &str) -> Option<Self> {
276        Self::from_sci_string_simplest_with_options(s, FromSciStringOptions::default())
277    }
278}