1use core::{cmp::Ordering, fmt, fmt::Write, ops::Neg};
2
3#[cfg(not(feature = "std"))]
4use alloc::{string::String, vec::Vec};
5
6use crate::decimal::round::round_pair_digits;
7#[cfg(not(feature = "numtraits"))]
8use crate::decimal::utils::cast::ToPrimitive;
9#[cfg(feature = "numtraits")]
10use num_traits::ToPrimitive;
11
12use crate::decimal::{RoundingMode, Sign};
13
14include!(concat!(env!("OUT_DIR"), "/exponential_format_threshold.rs"));
15
16pub(crate) fn write_scientific_notation<W: Write>(
17 digits: String,
18 scale: i16,
19 w: &mut W,
20) -> fmt::Result {
21 let (first_digit, remaining_digits) = digits.as_str().split_at(1);
22 w.write_str(first_digit)?;
23 if !remaining_digits.is_empty() {
24 w.write_str(".")?;
25 w.write_str(remaining_digits)?;
26 }
27 write!(w, "e{}", remaining_digits.len() as i32 - scale as i32)
28}
29
30pub(crate) fn write_engineering_notation<W: Write>(
31 digits: String,
32 scale: i16,
33 out: &mut W,
34) -> fmt::Result {
35 let digit_count = digits.len();
36
37 let top_digit_exponent = digit_count as i32 - scale as i32;
38
39 let shift_amount = match top_digit_exponent.rem_euclid(3) {
40 0 => 3,
41 i => i as usize,
42 };
43
44 let exp = top_digit_exponent - shift_amount as i32;
45
46 if let Some(padding_zero_count) = shift_amount.checked_sub(digits.len()) {
48 let zeros = &"000"[..padding_zero_count];
49 out.write_str(&digits)?;
50 out.write_str(zeros)?;
51 return write!(out, "e{exp}");
52 }
53
54 let (head, rest) = digits.split_at(shift_amount);
55 debug_assert_eq!(exp % 3, 0);
56
57 out.write_str(head)?;
58
59 if !rest.is_empty() {
60 out.write_char('.')?;
61 out.write_str(rest)?;
62 }
63
64 write!(out, "e{exp}")
65}
66
67pub(crate) fn format(
68 digits: String,
69 scale: i16,
70 sign: Sign,
71 f: &mut fmt::Formatter,
72) -> fmt::Result {
73 let leading_zero_count = scale
75 .to_u64()
76 .and_then(|scale| scale.checked_sub(digits.len() as u64))
77 .unwrap_or(0);
78
79 let trailing_zero_count = scale.checked_neg().and_then(|d| d.to_u64());
81
82 let trailing_zeros = f
84 .precision()
85 .map(|_| 0)
86 .or(trailing_zero_count)
87 .unwrap_or(0);
88
89 let leading_zero_threshold = EXPONENTIAL_FORMAT_LEADING_ZERO_THRESHOLD as u64;
90 let trailing_zero_threshold = EXPONENTIAL_FORMAT_TRAILING_ZERO_THRESHOLD as u64;
91
92 if leading_zero_threshold < leading_zero_count {
95 format_exponential(digits, scale, sign, f, "E")
96 } else if trailing_zero_threshold < trailing_zeros {
97 format_dotless_exponential(digits, scale, sign, f, "e")
99 } else {
100 format_full_scale(digits, scale, sign, f)
101 }
102}
103
104pub(crate) fn format_exponential(
105 digits: String,
106 scale: i16,
107 sign: Sign,
108 f: &mut fmt::Formatter,
109 e_symbol: &str,
110) -> fmt::Result {
111 let exp = (scale as i128).neg();
112 format_exponential_be_ascii_digits(digits, exp, sign, f, e_symbol)
113}
114
115fn format_dotless_exponential(
116 mut digits: String,
117 scale: i16,
118 sign: Sign,
119 f: &mut fmt::Formatter,
120 e_symbol: &str,
121) -> fmt::Result {
122 debug_assert!(scale <= 0);
123 write!(digits, "{}{:+}", e_symbol, -scale)?;
124 let non_negative = matches!(sign, Sign::Plus);
125 f.pad_integral(non_negative, "", &digits)
126}
127
128fn format_full_scale(
129 digits: String,
130 scale: i16,
131 sign: Sign,
132 f: &mut fmt::Formatter,
133) -> fmt::Result {
134 let mut digits = digits.into_bytes();
135 let mut exp = (scale as i128).neg();
136
137 if scale <= 0 {
138 zero_right_pad_integer_ascii_digits(&mut digits, &mut exp, f.precision());
140 } else {
141 let scale = scale as u64;
142 let prec = f
145 .precision()
146 .and_then(|prec| prec.to_u64())
147 .unwrap_or(scale);
148
149 if scale < digits.len() as u64 {
150 trim_ascii_digits(&mut digits, scale, sign, prec, &mut exp);
152 } else {
153 shift_or_trim_fractional_digits(&mut digits, scale, sign, prec, &mut exp);
155 }
156 exp = 0;
158 }
159
160 let mut buf = String::from_utf8(digits).unwrap();
162
163 if exp != 0 {
165 write!(buf, "e{exp:+}")?;
166 }
167
168 let non_negative = matches!(sign, Sign::Plus);
170 f.pad_integral(non_negative, "", &buf)
171}
172
173fn zero_right_pad_integer_ascii_digits(
178 digits: &mut Vec<u8>,
179 exp: &mut i128,
180 precision: Option<usize>,
181) {
182 debug_assert!(*exp >= 0);
183
184 let trailing_zero_count = match exp.to_usize() {
185 Some(n) => n,
186 None => {
187 return;
188 }
189 };
190 let total_additional_zeros = trailing_zero_count.saturating_add(precision.unwrap_or(0));
191 if total_additional_zeros > FMT_MAX_INTEGER_PADDING {
192 return;
193 }
194
195 match precision {
197 None if trailing_zero_count > 20 => {}
198 None | Some(0) => {
199 digits.resize(digits.len() + trailing_zero_count, b'0');
200 *exp = 0;
201 }
202 Some(prec) => {
203 digits.resize(digits.len() + trailing_zero_count, b'0');
204 digits.push(b'.');
205 digits.resize(digits.len() + prec, b'0');
206 *exp = 0;
207 }
208 }
209}
210
211fn trim_ascii_digits(digits: &mut Vec<u8>, scale: u64, sign: Sign, prec: u64, exp: &mut i128) {
212 debug_assert!(scale < digits.len() as u64);
213 let mut integer_digit_count = (digits.len() as u64 - scale)
215 .to_usize()
216 .expect("Number of digits exceeds maximum usize");
217
218 if prec < scale {
219 let prec = prec.to_usize().expect("Precision exceeds maximum usize");
220 if apply_rounding_to_ascii_digits(digits, exp, sign, integer_digit_count + prec) {
221 digits[0] = b'1';
222 integer_digit_count += 1;
223 digits.push(b'0');
224 }
225 }
226
227 if prec != 0 {
228 digits.insert(integer_digit_count, b'.');
229 }
230
231 if scale < prec {
232 let trailing_zero_count = (prec - scale).to_usize().expect("Too Big");
233
234 digits.resize(digits.len() + trailing_zero_count, b'0');
236 }
237}
238
239fn shift_or_trim_fractional_digits(
240 digits: &mut Vec<u8>,
241 scale: u64,
242 sign: Sign,
243 prec: u64,
244 exp: &mut i128,
245) {
246 debug_assert!(scale >= digits.len() as u64);
247 let leading_zeros = scale - digits.len() as u64;
249
250 match prec.checked_sub(leading_zeros) {
251 None => {
252 digits.clear();
253 digits.push(b'0');
254 if prec > 0 {
255 digits.push(b'.');
256 digits.resize(2 + prec as usize, b'0');
257 }
258 }
259 Some(0) => {
260 let insig_digit = digits[0] - b'0';
262 let trailing_zeros = digits[1..].iter().all(|&d| d == b'0');
263
264 let rounded_value = round_pair_digits(
265 (0, insig_digit),
266 sign,
267 RoundingMode::default(),
268 trailing_zeros,
269 );
270
271 digits.clear();
272 if leading_zeros != 0 {
273 digits.push(b'0');
274 digits.push(b'.');
275 digits.resize(1 + leading_zeros as usize, b'0');
276 }
277 digits.push(rounded_value + b'0');
278 }
279 Some(digit_prec) => {
280 let mut carry = false;
281 let digit_prec = digit_prec as usize;
282 let mut leading_zeros = leading_zeros
283 .to_usize()
284 .expect("Number of leading zeros exceeds max usize");
285 let trailing_zeros = digit_prec.saturating_sub(digits.len());
286 if digit_prec < digits.len() {
287 carry = apply_rounding_to_ascii_digits(digits, exp, sign, digit_prec);
288 }
289 if carry {
290 if prec <= digit_prec as u64 {
291 digits.extend_from_slice(b"1.");
292 } else {
293 digits.insert(0, b'1');
294 leading_zeros = leading_zeros.saturating_sub(1);
295 digits.extend_from_slice(b"0.");
296 }
297 } else {
298 digits.extend_from_slice(b"0.");
299 }
300 digits.resize(digits.len() + leading_zeros, b'0');
301 digits.rotate_right(leading_zeros + 2);
302
303 digits.resize(digits.len() + trailing_zeros, b'0');
305 }
306 }
307}
308
309fn format_exponential_be_ascii_digits(
310 digits: String,
311 mut exp: i128,
312 sign: Sign,
313 f: &mut fmt::Formatter,
314 e_symbol: &str,
315) -> fmt::Result {
316 let mut digits = digits.into_bytes();
317
318 let mut extra_trailing_zero_count = 0;
320
321 if let Some(prec) = f.precision() {
322 let total_prec = prec + 1;
324 let digit_count = digits.len();
325
326 match total_prec.cmp(&digit_count) {
327 Ordering::Equal => {
328 }
330 Ordering::Less => {
331 if apply_rounding_to_ascii_digits(&mut digits, &mut exp, sign, total_prec) {
333 digits[0] = b'1';
334 }
335 }
336 Ordering::Greater => {
337 extra_trailing_zero_count = total_prec - digit_count;
339 }
340 }
341 }
342
343 let needs_decimal_point = digits.len() > 1 || extra_trailing_zero_count > 0;
344
345 let mut abs_int = String::from_utf8(digits).unwrap();
346
347 let exponent = abs_int.len() as i128 + exp - 1;
348
349 if needs_decimal_point {
350 abs_int.insert(1, '.');
352 }
353
354 if extra_trailing_zero_count > 0 {
355 abs_int.extend(core::iter::repeat_n('0', extra_trailing_zero_count));
356 }
357
358 write!(abs_int, "{e_symbol}{exponent:+}")?;
360
361 let non_negative = matches!(sign, Sign::Plus);
362 f.pad_integral(non_negative, "", &abs_int)
363}
364
365#[must_use = "must use carry result"]
366fn apply_rounding_to_ascii_digits(
367 ascii_digits: &mut Vec<u8>,
368 exp: &mut i128,
369 sign: Sign,
370 prec: usize,
371) -> bool {
372 if ascii_digits.len() < prec {
373 return false;
374 }
375
376 *exp += (ascii_digits.len() - prec) as i128;
378
379 let trailing_zeros = ascii_digits[prec + 1..].iter().all(|&d| d == b'0');
381
382 let sig_digit = ascii_digits[prec - 1] - b'0';
383 let insig_digit = ascii_digits[prec] - b'0';
384
385 let rounded_digit = round_pair_digits(
386 (sig_digit, insig_digit),
387 sign,
388 RoundingMode::default(),
389 trailing_zeros,
390 );
391
392 ascii_digits.truncate(prec - 1);
394
395 if rounded_digit < 10 {
397 ascii_digits.push(rounded_digit + b'0');
398 return false;
399 }
400
401 debug_assert_eq!(rounded_digit, 10);
402
403 ascii_digits.push(b'0');
405
406 let digits = ascii_digits.iter_mut().rev().skip(1);
408 for digit in digits {
409 if *digit < b'9' {
410 *digit += 1;
412 return false;
413 }
414
415 debug_assert_eq!(*digit, b'9');
416
417 *digit = b'0';
420 }
421
422 *exp += 1;
423 true
424}