malachite_float/float/conversion/string/
to_sci.rs1use crate::Float;
20use crate::InnerFloat::{Finite, Infinity, NaN, Zero};
21use crate::float::conversion::string::format_float::strip_trailing_zeros;
22use crate::float::conversion::string::get_str::get_str;
23use alloc::string::String;
24use alloc::vec;
25use alloc::vec::Vec;
26use core::cmp::Ordering::*;
27use core::fmt::Write;
28use malachite_base::num::arithmetic::traits::{Abs, DivRound, Pow};
29use malachite_base::num::conversion::string::options::{SciSizeOptions, ToSciOptions};
30use malachite_base::num::conversion::traits::{ExactFrom, IntegerMantissaAndExponent};
31use malachite_base::rounding_modes::RoundingMode::*;
32use malachite_q::Rational;
33
34fn length_after_point(k: i64, base: i64) -> Option<u64> {
41 if k >= 0 {
42 Some(0)
43 } else {
44 match u64::from(base.trailing_zeros()) {
45 0 => None,
46 v => Some(k.unsigned_abs().div_round(v, Ceiling).0),
47 }
48 }
49}
50
51fn floor_log_base(x: &Float, base: i64) -> i64 {
55 get_str(x, base, 1, Down).unwrap().1 - 1
56}
57
58fn push_exponent(out: &mut String, options: ToSciOptions, exp: i64) {
62 out.push(if options.get_e_lowercase() { 'e' } else { 'E' });
63 if exp > 0 && (options.get_force_exponent_plus_sign() || options.get_base() >= 15) {
64 out.push('+');
65 }
66 write!(out, "{exp}").unwrap();
67}
68
69fn zero_to_string(neg: bool, options: ToSciOptions) -> String {
72 let mut out = String::new();
73 if neg {
74 out.push('-');
75 }
76 out.push('0');
77 if options.get_include_trailing_zeros() {
78 let zeros = match options.get_size_options() {
79 SciSizeOptions::Complete => 0,
80 SciSizeOptions::Scale(scale) => scale,
81 SciSizeOptions::Precision(precision) => precision - 1,
82 };
83 if zeros != 0 {
84 out.push('.');
85 for _ in 0..zeros {
86 out.push('0');
87 }
88 }
89 }
90 if !out.contains('.') {
91 out.push_str(".0");
92 }
93 out
94}
95
96pub_crate_test! {
97to_sci_valid(x: &Float, options: ToSciOptions) -> bool {
102 if !matches!(x, Float(Finite { .. })) {
103 return true;
105 }
106 let base = i64::from(options.get_base());
107 let min_scale = length_after_point(x.integer_exponent(), base);
108 if let SciSizeOptions::Complete = options.get_size_options() {
109 return min_scale.is_some();
110 }
111 if options.get_rounding_mode() != Exact {
112 return true;
113 }
114 let Some(min_scale) = min_scale else {
115 return false;
116 };
117 let min_scale = i64::exact_from(min_scale);
118 match options.get_size_options() {
119 SciSizeOptions::Scale(scale) => min_scale <= i64::exact_from(scale),
120 SciSizeOptions::Precision(precision) => {
121 min_scale <= i64::exact_from(precision - 1) - floor_log_base(x, base)
122 }
123 SciSizeOptions::Complete => unreachable!(),
124 }
125}}
126
127pub_crate_test! {
128to_sci_string(x: &Float, options: ToSciOptions) -> String {
143 let (neg, sign) = match x {
144 Float(NaN) => return String::from("NaN"),
145 Float(Infinity { sign: true }) => return String::from("Infinity"),
146 Float(Infinity { sign: false }) => return String::from("-Infinity"),
147 Float(Zero { sign }) => return zero_to_string(!*sign, options),
148 Float(Finite { sign, .. }) => (!*sign, *sign),
149 };
150 let base = i64::from(options.get_base());
151 let rm = options.get_rounding_mode();
152 let trim_zeros = !options.get_include_trailing_zeros()
153 && options.get_size_options() != SciSizeOptions::Complete;
154 let log = floor_log_base(x, base);
155 let (scale, precision) = match options.get_size_options() {
159 SciSizeOptions::Complete => {
160 let scale = length_after_point(x.integer_exponent(), base).unwrap_or_else(|| {
161 panic!("{x} has a non-terminating expansion in base {base}")
162 });
163 let precision = i64::exact_from(scale) + log + 1;
164 assert!(precision > 0);
166 (i64::exact_from(scale), precision)
167 }
168 SciSizeOptions::Scale(scale) => {
169 (i64::exact_from(scale), i64::exact_from(scale) + log + 1)
170 }
171 SciSizeOptions::Precision(precision) => (
172 i64::exact_from(precision - 1) - log,
173 i64::exact_from(precision),
174 ),
175 };
176 let (digits, log) = if precision <= 0 {
177 let round_up_to_one = match rm {
179 Up => true,
180 Down => false,
181 Floor => neg,
182 Ceiling => !neg,
183 Exact => panic!(
184 "Exact rounding was requested, but {x} is not exactly representable with {scale} \
185 digits after the point",
186 ),
187 Nearest => {
191 log + 1 == -scale && {
192 let two_x = Rational::exact_from(x).abs() << 1u32;
193 two_x > Rational::from(base).pow(-scale)
194 }
195 }
196 };
197 if round_up_to_one {
198 (vec![b'1'], -scale)
199 } else {
200 return zero_to_string(neg, options);
201 }
202 } else {
203 let m = usize::exact_from(precision);
204 let get_str_base = if options.get_lowercase() { base } else { -base };
206 let (s, e, o) = get_str(x, get_str_base, m, rm).unwrap();
207 let mut digits = if neg { s[1..].to_vec() } else { s };
208 debug_assert!(options.get_size_options() != SciSizeOptions::Complete || o == Equal);
209 let new_log = e - 1;
210 if new_log > log && matches!(options.get_size_options(), SciSizeOptions::Scale(_)) {
216 digits.push(b'0');
217 }
218 (digits, new_log)
219 };
220 let target_scale = match options.get_size_options() {
222 SciSizeOptions::Precision(_) => i64::exact_from(digits.len()) - 1 - log,
223 _ => scale,
224 };
225 let mut mantissa: Vec<u8> = Vec::new();
226 let mut exponent = None;
227 if log <= options.get_neg_exp_threshold() || target_scale < 0 {
228 let ds = if trim_zeros {
230 strip_trailing_zeros(&digits)
231 } else {
232 &digits
233 };
234 mantissa.push(ds[0]);
235 if ds.len() > 1 {
236 mantissa.push(b'.');
237 mantissa.extend_from_slice(&ds[1..]);
238 }
239 exponent = Some(log);
240 } else if log < 0 {
241 let ds = if trim_zeros {
243 strip_trailing_zeros(&digits)
244 } else {
245 &digits
246 };
247 mantissa.extend_from_slice(b"0.");
248 mantissa.resize(2 + usize::exact_from(-log - 1), b'0');
249 mantissa.extend_from_slice(ds);
250 debug_assert!(
251 trim_zeros || -log - 1 + i64::exact_from(ds.len()) == target_scale,
252 "fractional length mismatch"
253 );
254 } else {
255 let digits_before = usize::exact_from(log + 1);
257 mantissa.extend_from_slice(&digits[..digits_before]);
258 let frac = if trim_zeros {
259 strip_trailing_zeros(&digits[digits_before..])
260 } else {
261 &digits[digits_before..]
262 };
263 if !frac.is_empty() {
264 mantissa.push(b'.');
265 mantissa.extend_from_slice(frac);
266 }
267 debug_assert!(
268 trim_zeros || i64::exact_from(frac.len()) == target_scale,
269 "fractional length mismatch"
270 );
271 }
272 if !mantissa.contains(&b'.') {
274 mantissa.extend_from_slice(b".0");
275 }
276 let mut out = String::new();
277 if !sign {
278 out.push('-');
279 }
280 out.push_str(core::str::from_utf8(&mantissa).unwrap());
281 if let Some(exp) = exponent {
282 push_exponent(&mut out, options, exp);
283 }
284 out
285}}