malachite_q/conversion/string/to_sci.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::arithmetic::log_base::log_base_helper;
10use crate::Rational;
11use alloc::string::String;
12use core::cmp::{max, Ordering::*};
13use core::fmt::{Formatter, Write};
14use malachite_base::num::arithmetic::traits::{
15 Abs, CheckedLogBase2, DivExact, DivExactAssign, DivRound, DivisibleBy, Pow, Sign,
16};
17use malachite_base::num::conversion::string::options::{SciSizeOptions, ToSciOptions};
18use malachite_base::num::conversion::string::to_sci::write_exponent;
19use malachite_base::num::conversion::traits::{
20 ExactFrom, IsInteger, RoundingFrom, ToSci, ToStringBase, WrappingFrom,
21};
22use malachite_base::rounding_modes::RoundingMode::*;
23use malachite_nz::integer::Integer;
24use malachite_nz::natural::Natural;
25
26const BASE_PRIME_FACTORS: [[(u8, u8); 3]; 37] = [
27 [(0, 0), (0, 0), (0, 0)],
28 [(0, 0), (0, 0), (0, 0)],
29 [(2, 1), (0, 0), (0, 0)],
30 [(3, 1), (0, 0), (0, 0)],
31 [(2, 2), (0, 0), (0, 0)],
32 [(5, 1), (0, 0), (0, 0)],
33 [(2, 1), (3, 1), (0, 0)],
34 [(7, 1), (0, 0), (0, 0)],
35 [(2, 3), (0, 0), (0, 0)],
36 [(3, 2), (0, 0), (0, 0)],
37 [(2, 1), (5, 1), (0, 0)],
38 [(11, 1), (0, 0), (0, 0)],
39 [(2, 2), (3, 1), (0, 0)],
40 [(13, 1), (0, 0), (0, 0)],
41 [(2, 1), (7, 1), (0, 0)],
42 [(3, 1), (5, 1), (0, 0)],
43 [(2, 4), (0, 0), (0, 0)],
44 [(17, 1), (0, 0), (0, 0)],
45 [(2, 1), (3, 2), (0, 0)],
46 [(19, 1), (0, 0), (0, 0)],
47 [(2, 2), (5, 1), (0, 0)],
48 [(3, 1), (7, 1), (0, 0)],
49 [(2, 1), (11, 1), (0, 0)],
50 [(23, 1), (0, 0), (0, 0)],
51 [(2, 3), (3, 1), (0, 0)],
52 [(5, 2), (0, 0), (0, 0)],
53 [(2, 1), (13, 1), (0, 0)],
54 [(3, 3), (0, 0), (0, 0)],
55 [(2, 2), (7, 1), (0, 0)],
56 [(29, 1), (0, 0), (0, 0)],
57 [(2, 1), (3, 1), (5, 1)],
58 [(31, 1), (0, 0), (0, 0)],
59 [(2, 5), (0, 0), (0, 0)],
60 [(3, 1), (11, 1), (0, 0)],
61 [(2, 1), (17, 1), (0, 0)],
62 [(5, 1), (7, 1), (0, 0)],
63 [(2, 2), (3, 2), (0, 0)],
64];
65
66impl Rational {
67 /// When expanding a [`Rational`] in a small base $b$, determines how many digits after the
68 /// decimal (or other-base) point are in the base-$b$ expansion.
69 ///
70 /// If the expansion is non-terminating, this method returns `None`. This happens iff the
71 /// [`Rational`]'s denominator has prime factors not present in $b$.
72 ///
73 /// # Worst-case complexity
74 /// $T(n) = O(n)$
75 ///
76 /// $M(n) = O(n)$
77 ///
78 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
79 ///
80 /// # Panics
81 /// Panics if `base` is less than 2 or greater than 36.
82 ///
83 /// # Examples
84 /// ```
85 /// use malachite_q::Rational;
86 ///
87 /// // 3/8 is 0.375 in base 10.
88 /// assert_eq!(
89 /// Rational::from_signeds(3, 8).length_after_point_in_small_base(10),
90 /// Some(3)
91 /// );
92 /// // 1/20 is 0.05 in base 10.
93 /// assert_eq!(
94 /// Rational::from_signeds(1, 20).length_after_point_in_small_base(10),
95 /// Some(2)
96 /// );
97 /// // 1/7 is non-terminating in base 10.
98 /// assert_eq!(
99 /// Rational::from_signeds(1, 7).length_after_point_in_small_base(10),
100 /// None
101 /// );
102 /// // 1/7 is 0.3 in base 21.
103 /// assert_eq!(
104 /// Rational::from_signeds(1, 7).length_after_point_in_small_base(21),
105 /// Some(1)
106 /// );
107 /// ```
108 pub fn length_after_point_in_small_base(&self, base: u8) -> Option<u64> {
109 let d = self.denominator_ref();
110 assert!((2..=36).contains(&base));
111 if *d == 1u32 {
112 return Some(0);
113 }
114 let mut temp = None;
115 let mut length = 0;
116 for (f, m) in BASE_PRIME_FACTORS[usize::wrapping_from(base)] {
117 let count = if f == 0 {
118 break;
119 } else if f == 2 {
120 let twos = d.trailing_zeros().unwrap();
121 if twos != 0 {
122 temp = Some(d >> twos);
123 }
124 twos
125 } else {
126 let f = Natural::from(f);
127 let mut count = 0;
128 if let Some(temp) = temp.as_mut() {
129 while (&*temp).divisible_by(&f) {
130 temp.div_exact_assign(&f);
131 count += 1;
132 }
133 } else if d.divisible_by(&f) {
134 count = 1;
135 let mut t = d.div_exact(&f);
136 while (&t).divisible_by(&f) {
137 t.div_exact_assign(&f);
138 count += 1;
139 }
140 temp = Some(t);
141 }
142 count
143 };
144 length = max(length, count.div_round(u64::from(m), Ceiling).0);
145 }
146 if let Some(temp) = temp {
147 if temp == 1 {
148 Some(length)
149 } else {
150 None
151 }
152 } else {
153 None
154 }
155 }
156}
157
158pub_test! {floor_log_base_of_abs(x: &Rational, base: &Rational) -> i64 {
159 if let Some(log_base) = base.checked_log_base_2() {
160 match log_base.sign() {
161 Equal => panic!("Cannot take base-1 logarithm"),
162 Greater => x
163 .floor_log_base_2_abs()
164 .div_round(log_base, Floor).0,
165 Less => {
166 -(x.ceiling_log_base_2_abs()
167 .div_round(-log_base, Ceiling).0)
168 }
169 }
170 } else {
171 log_base_helper(x, base).0
172 }
173}}
174
175fn fmt_zero(f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result {
176 f.write_char('0')?;
177 let scale = if options.get_include_trailing_zeros() {
178 match options.get_size_options() {
179 SciSizeOptions::Complete => None,
180 SciSizeOptions::Scale(scale) => {
181 if scale == 0 {
182 None
183 } else {
184 Some(scale)
185 }
186 }
187 SciSizeOptions::Precision(precision) => {
188 if precision == 1 {
189 None
190 } else {
191 Some(precision - 1)
192 }
193 }
194 }
195 } else {
196 None
197 };
198 if let Some(scale) = scale {
199 f.write_char('.')?;
200 for _ in 0..scale {
201 f.write_char('0')?;
202 }
203 }
204 Ok(())
205}
206
207impl ToSci for Rational {
208 /// Determines whether a [`Rational`] can be converted to a string using
209 /// [`to_sci`](malachite_base::num::conversion::traits::ToSci::to_sci) and a particular set of
210 /// options.
211 ///
212 /// # Worst-case complexity
213 /// $T(n) = O(n \log n \log\log n)$
214 ///
215 /// $M(n) = O(n \log n)$
216 ///
217 /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(), s)`,
218 /// where `s` depends on the size type specified in `options`.
219 /// - If `options` has `scale` specified, then `s` is `options.scale`.
220 /// - If `options` has `precision` specified, then `s` is `options.precision`.
221 /// - If `options` has `size_complete` specified, then `s` is `self.denominator` (not the log of
222 /// the denominator!). This reflects the fact that setting `size_complete` might result in a
223 /// very long string.
224 ///
225 /// # Examples
226 /// ```
227 /// use malachite_base::num::conversion::string::options::ToSciOptions;
228 /// use malachite_base::num::conversion::traits::ToSci;
229 /// use malachite_base::rounding_modes::RoundingMode::*;
230 /// use malachite_q::Rational;
231 ///
232 /// let mut options = ToSciOptions::default();
233 /// assert!(Rational::from(123u8).fmt_sci_valid(options));
234 /// assert!(Rational::from(u128::MAX).fmt_sci_valid(options));
235 /// // u128::MAX has more than 16 significant digits
236 /// options.set_rounding_mode(Exact);
237 /// assert!(!Rational::from(u128::MAX).fmt_sci_valid(options));
238 /// options.set_precision(50);
239 /// assert!(Rational::from(u128::MAX).fmt_sci_valid(options));
240 ///
241 /// let mut options = ToSciOptions::default();
242 /// options.set_size_complete();
243 /// // 1/3 is non-terminating in base 10...
244 /// assert!(!Rational::from_signeds(1, 3).fmt_sci_valid(options));
245 /// options.set_size_complete();
246 ///
247 /// // ...but is terminating in base 36
248 /// options.set_base(36);
249 /// assert!(Rational::from_signeds(1, 3).fmt_sci_valid(options));
250 /// ```
251 fn fmt_sci_valid(&self, options: ToSciOptions) -> bool {
252 if *self == 0 {
253 return true;
254 }
255 if let SciSizeOptions::Complete = options.get_size_options() {
256 return self
257 .length_after_point_in_small_base(options.get_base())
258 .is_some();
259 }
260 if options.get_rounding_mode() != Exact {
261 return true;
262 }
263 let q_base = Rational::from(options.get_base());
264 let scale = match options.get_size_options() {
265 SciSizeOptions::Precision(precision) => {
266 let log = floor_log_base_of_abs(self, &q_base);
267 i64::exact_from(precision - 1) - log
268 }
269 SciSizeOptions::Scale(scale) => i64::exact_from(scale),
270 _ => unreachable!(),
271 };
272 (self * q_base.pow(scale)).is_integer()
273 }
274
275 /// Converts a [`Rational` ]to a string using a specified base, possibly formatting the number
276 /// using scientific notation.
277 ///
278 /// See [`ToSciOptions`] for details on the available options.
279 ///
280 /// # Worst-case complexity
281 /// $T(n) = O(n \log n \log\log n)$
282 ///
283 /// $M(n) = O(n \log n)$
284 ///
285 /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(), s)`,
286 /// where `s` depends on the size type specified in `options`.
287 /// - If `options` has `scale` specified, then `s` is `options.scale`.
288 /// - If `options` has `precision` specified, then `s` is `options.precision`.
289 /// - If `options` has `size_complete` specified, then `s` is `self.denominator` (not the log of
290 /// the denominator!). This reflects the fact that setting `size_complete` might result in a
291 /// very long string.
292 ///
293 /// # Panics
294 /// Panics if `options.rounding_mode` is `Exact`, but the size options are such that the input
295 /// must be rounded.
296 ///
297 /// # Examples
298 /// ```
299 /// use malachite_base::num::arithmetic::traits::PowerOf2;
300 /// use malachite_base::num::conversion::string::options::ToSciOptions;
301 /// use malachite_base::num::conversion::traits::ToSci;
302 /// use malachite_base::rounding_modes::RoundingMode::*;
303 /// use malachite_q::Rational;
304 ///
305 /// let q = Rational::from_signeds(22, 7);
306 /// let mut options = ToSciOptions::default();
307 /// assert_eq!(
308 /// q.to_sci_with_options(options).to_string(),
309 /// "3.142857142857143"
310 /// );
311 ///
312 /// options.set_precision(3);
313 /// assert_eq!(q.to_sci_with_options(options).to_string(), "3.14");
314 ///
315 /// options.set_rounding_mode(Ceiling);
316 /// assert_eq!(q.to_sci_with_options(options).to_string(), "3.15");
317 ///
318 /// options = ToSciOptions::default();
319 /// options.set_base(20);
320 /// assert_eq!(
321 /// q.to_sci_with_options(options).to_string(),
322 /// "3.2h2h2h2h2h2h2h3"
323 /// );
324 ///
325 /// options.set_uppercase();
326 /// assert_eq!(
327 /// q.to_sci_with_options(options).to_string(),
328 /// "3.2H2H2H2H2H2H2H3"
329 /// );
330 ///
331 /// options.set_base(2);
332 /// options.set_rounding_mode(Floor);
333 /// options.set_precision(19);
334 /// assert_eq!(
335 /// q.to_sci_with_options(options).to_string(),
336 /// "11.001001001001001"
337 /// );
338 ///
339 /// options.set_include_trailing_zeros(true);
340 /// assert_eq!(
341 /// q.to_sci_with_options(options).to_string(),
342 /// "11.00100100100100100"
343 /// );
344 ///
345 /// let q = Rational::from_unsigneds(936851431250u64, 1397u64);
346 /// let mut options = ToSciOptions::default();
347 /// options.set_precision(6);
348 /// assert_eq!(q.to_sci_with_options(options).to_string(), "6.70617e8");
349 ///
350 /// options.set_e_uppercase();
351 /// assert_eq!(q.to_sci_with_options(options).to_string(), "6.70617E8");
352 ///
353 /// options.set_force_exponent_plus_sign(true);
354 /// assert_eq!(q.to_sci_with_options(options).to_string(), "6.70617E+8");
355 ///
356 /// let q = Rational::from_signeds(123i64, 45678909876i64);
357 /// let mut options = ToSciOptions::default();
358 /// assert_eq!(
359 /// q.to_sci_with_options(options).to_string(),
360 /// "2.692708743135418e-9"
361 /// );
362 ///
363 /// options.set_neg_exp_threshold(-10);
364 /// assert_eq!(
365 /// q.to_sci_with_options(options).to_string(),
366 /// "0.000000002692708743135418"
367 /// );
368 ///
369 /// let q = Rational::power_of_2(-30i64);
370 /// let mut options = ToSciOptions::default();
371 /// assert_eq!(
372 /// q.to_sci_with_options(options).to_string(),
373 /// "9.313225746154785e-10"
374 /// );
375 ///
376 /// options.set_size_complete();
377 /// assert_eq!(
378 /// q.to_sci_with_options(options).to_string(),
379 /// "9.31322574615478515625e-10"
380 /// );
381 /// ```
382 fn fmt_sci(&self, f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result {
383 if *self == 0u32 {
384 return fmt_zero(f, options);
385 }
386 if *self < 0u32 {
387 f.write_char('-')?;
388 }
389 let base = options.get_base();
390 let q_base = Rational::from(base);
391 let mut trim_zeros = !options.get_include_trailing_zeros();
392 let mut log = floor_log_base_of_abs(self, &q_base);
393 // Here, precision 0 means that we're rounding down to zero
394 let (mut scale, mut precision) = match options.get_size_options() {
395 SciSizeOptions::Complete => {
396 trim_zeros = false;
397 let scale = self
398 .length_after_point_in_small_base(base)
399 .unwrap_or_else(|| {
400 panic!("{self} has a non-terminating expansion in base {base}")
401 });
402 let precision = i64::exact_from(scale) + log + 1;
403 assert!(precision > 0);
404 (i64::exact_from(scale), precision)
405 }
406 SciSizeOptions::Scale(scale) => {
407 (i64::exact_from(scale), i64::exact_from(scale) + log + 1)
408 }
409 SciSizeOptions::Precision(precision) => (
410 i64::exact_from(precision - 1) - log,
411 i64::exact_from(precision),
412 ),
413 };
414 let n = Integer::rounding_from(self * q_base.pow(scale), options.get_rounding_mode())
415 .0
416 .abs();
417 if precision <= 0 {
418 // e.g. we're in base 10, self is 0.01 or 0.000001, but scale is 1
419 if n == 0u32 {
420 return fmt_zero(f, options);
421 } else if n == 1u32 {
422 precision = 1;
423 log = -scale;
424 } else {
425 panic!("Bug: precision <= 0 must mean self.abs() rounds to 0 or 1");
426 };
427 }
428 let mut cs = if options.get_lowercase() {
429 n.to_string_base(base)
430 } else {
431 n.to_string_base_upper(base)
432 }
433 .into_bytes();
434 let mut precision = usize::exact_from(precision);
435 if cs.len() == precision + 1 {
436 // We rounded up to a power of the base, so precision is greater than we expected. If
437 // the options specify the precision, we need to adjust.
438 log += 1;
439 match options.get_size_options() {
440 SciSizeOptions::Complete => panic!(),
441 SciSizeOptions::Precision(_) => {
442 scale -= 1;
443 assert_eq!(cs.pop().unwrap(), b'0');
444 }
445 SciSizeOptions::Scale(_) => {
446 precision += 1;
447 }
448 }
449 }
450 assert_eq!(cs.len(), precision);
451 if log <= options.get_neg_exp_threshold() || scale < 0 {
452 assert_ne!(log, 0);
453 // exponent
454 if trim_zeros {
455 let trailing_zeros = cs.iter().rev().take_while(|&&c| c == b'0').count();
456 precision -= trailing_zeros;
457 cs.truncate(precision);
458 }
459 if precision > 1 {
460 cs.push(0);
461 cs.copy_within(1..precision, 2);
462 cs[1] = b'.';
463 }
464 f.write_str(&String::from_utf8(cs).unwrap())?;
465 write_exponent(f, options, log)
466 } else if scale == 0 {
467 // no exponent or point
468 f.write_str(&String::from_utf8(cs).unwrap())
469 } else {
470 // no exponent
471 if trim_zeros {
472 let trailing_zeros = cs
473 .iter()
474 .rev()
475 .take(usize::exact_from(scale))
476 .take_while(|&&c| c == b'0')
477 .count();
478 precision -= trailing_zeros;
479 cs.truncate(precision);
480 }
481 if log < 0 {
482 f.write_char('0')?;
483 f.write_char('.')?;
484 for _ in 0..-log - 1 {
485 f.write_char('0')?;
486 }
487 } else {
488 let digits_before = usize::exact_from(log) + 1;
489 if precision > digits_before {
490 cs.push(0);
491 cs.copy_within(digits_before..precision, digits_before + 1);
492 cs[digits_before] = b'.';
493 }
494 }
495 f.write_str(&String::from_utf8(cs).unwrap())
496 }
497 }
498}