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}