use core::{cmp::Ordering, fmt, fmt::Write, ops::Neg};
use crate::decimal::round::round_pair_digits;
#[cfg(not(feature = "numtraits"))]
use crate::decimal::utils::cast::ToPrimitive;
#[cfg(feature = "numtraits")]
use num_traits::ToPrimitive;
use crate::decimal::{RoundingMode, Sign};
include!(concat!(env!("OUT_DIR"), "/exponential_format_threshold.rs"));
pub(crate) fn write_scientific_notation<W: Write>(
digits: String,
scale: i16,
w: &mut W,
) -> fmt::Result {
let (first_digit, remaining_digits) = digits.as_str().split_at(1);
w.write_str(first_digit)?;
if !remaining_digits.is_empty() {
w.write_str(".")?;
w.write_str(remaining_digits)?;
}
write!(w, "e{}", remaining_digits.len() as i32 - scale as i32)
}
pub(crate) fn write_engineering_notation<W: Write>(
digits: String,
scale: i16,
out: &mut W,
) -> fmt::Result {
let digit_count = digits.len();
let top_digit_exponent = digit_count as i32 - scale as i32;
let shift_amount = match top_digit_exponent.rem_euclid(3) {
0 => 3,
i => i as usize,
};
let exp = top_digit_exponent - shift_amount as i32;
if let Some(padding_zero_count) = shift_amount.checked_sub(digits.len()) {
let zeros = &"000"[..padding_zero_count];
out.write_str(&digits)?;
out.write_str(zeros)?;
return write!(out, "e{}", exp);
}
let (head, rest) = digits.split_at(shift_amount);
debug_assert_eq!(exp % 3, 0);
out.write_str(head)?;
if !rest.is_empty() {
out.write_char('.')?;
out.write_str(rest)?;
}
write!(out, "e{}", exp)
}
pub(crate) fn format(
digits: String,
scale: i16,
sign: Sign,
f: &mut fmt::Formatter,
) -> fmt::Result {
let leading_zero_count = scale
.to_u64()
.and_then(|scale| scale.checked_sub(digits.len() as u64))
.unwrap_or(0);
let trailing_zero_count = scale.checked_neg().and_then(|d| d.to_u64());
let trailing_zeros = f
.precision()
.map(|_| 0)
.or(trailing_zero_count)
.unwrap_or(0);
let leading_zero_threshold = EXPONENTIAL_FORMAT_LEADING_ZERO_THRESHOLD as u64;
let trailing_zero_threshold = EXPONENTIAL_FORMAT_TRAILING_ZERO_THRESHOLD as u64;
if leading_zero_threshold < leading_zero_count {
format_exponential(digits, scale, sign, f, "E")
} else if trailing_zero_threshold < trailing_zeros {
format_dotless_exponential(digits, scale, sign, f, "e")
} else {
format_full_scale(digits, scale, sign, f)
}
}
pub(crate) fn format_exponential(
digits: String,
scale: i16,
sign: Sign,
f: &mut fmt::Formatter,
e_symbol: &str,
) -> fmt::Result {
let exp = (scale as i128).neg();
format_exponential_be_ascii_digits(digits, exp, sign, f, e_symbol)
}
fn format_dotless_exponential(
mut digits: String,
scale: i16,
sign: Sign,
f: &mut fmt::Formatter,
e_symbol: &str,
) -> fmt::Result {
debug_assert!(scale <= 0);
write!(digits, "{}{:+}", e_symbol, -scale)?;
let non_negative = matches!(sign, Sign::Plus);
f.pad_integral(non_negative, "", &digits)
}
fn format_full_scale(
digits: String,
scale: i16,
sign: Sign,
f: &mut fmt::Formatter,
) -> fmt::Result {
let mut digits = digits.into_bytes();
let mut exp = (scale as i128).neg();
if scale <= 0 {
zero_right_pad_integer_ascii_digits(&mut digits, &mut exp, f.precision());
} else {
let scale = scale as u64;
let prec = f
.precision()
.and_then(|prec| prec.to_u64())
.unwrap_or(scale);
if scale < digits.len() as u64 {
trim_ascii_digits(&mut digits, scale, sign, prec, &mut exp);
} else {
shift_or_trim_fractional_digits(&mut digits, scale, sign, prec, &mut exp);
}
exp = 0;
}
let mut buf = String::from_utf8(digits).unwrap();
if exp != 0 {
write!(buf, "e{:+}", exp)?;
}
let non_negative = matches!(sign, Sign::Plus);
f.pad_integral(non_negative, "", &buf)
}
fn zero_right_pad_integer_ascii_digits(
digits: &mut Vec<u8>,
exp: &mut i128,
precision: Option<usize>,
) {
debug_assert!(*exp >= 0);
let trailing_zero_count = match exp.to_usize() {
Some(n) => n,
None => {
return;
}
};
let total_additional_zeros = trailing_zero_count.saturating_add(precision.unwrap_or(0));
if total_additional_zeros > FMT_MAX_INTEGER_PADDING {
return;
}
match precision {
None if trailing_zero_count > 20 => {}
None | Some(0) => {
digits.resize(digits.len() + trailing_zero_count, b'0');
*exp = 0;
}
Some(prec) => {
digits.resize(digits.len() + trailing_zero_count, b'0');
digits.push(b'.');
digits.resize(digits.len() + prec, b'0');
*exp = 0;
}
}
}
fn trim_ascii_digits(digits: &mut Vec<u8>, scale: u64, sign: Sign, prec: u64, exp: &mut i128) {
debug_assert!(scale < digits.len() as u64);
let integer_digit_count = (digits.len() as u64 - scale)
.to_usize()
.expect("Number of digits exceeds maximum usize");
if prec < scale {
let prec = prec.to_usize().expect("Precision exceeds maximum usize");
apply_rounding_to_ascii_digits(digits, exp, sign, integer_digit_count + prec);
}
if prec != 0 {
digits.insert(integer_digit_count, b'.');
}
if scale < prec {
let trailing_zero_count = (prec - scale).to_usize().expect("Too Big");
digits.resize(digits.len() + trailing_zero_count, b'0');
}
}
fn shift_or_trim_fractional_digits(
digits: &mut Vec<u8>,
scale: u64,
sign: Sign,
prec: u64,
exp: &mut i128,
) {
debug_assert!(scale >= digits.len() as u64);
let leading_zeros = scale - digits.len() as u64;
match prec.checked_sub(leading_zeros) {
None => {
digits.clear();
digits.push(b'0');
if prec > 0 {
digits.push(b'.');
digits.resize(2 + prec as usize, b'0');
}
}
Some(0) => {
let insig_digit = digits[0] - b'0';
let trailing_zeros = digits[1..].iter().all(|&d| d == b'0');
let rounded_value = round_pair_digits(
(0, insig_digit),
sign,
RoundingMode::default(),
trailing_zeros,
);
digits.clear();
if leading_zeros != 0 {
digits.push(b'0');
digits.push(b'.');
digits.resize(1 + leading_zeros as usize, b'0');
}
digits.push(rounded_value + b'0');
}
Some(digit_prec) => {
let digit_prec = digit_prec as usize;
let leading_zeros = leading_zeros
.to_usize()
.expect("Number of leading zeros exceeds max usize");
let trailing_zeros = digit_prec.saturating_sub(digits.len());
if digit_prec < digits.len() {
apply_rounding_to_ascii_digits(digits, exp, sign, digit_prec);
}
digits.extend_from_slice(b"0.");
digits.resize(digits.len() + leading_zeros, b'0');
digits.rotate_right(leading_zeros + 2);
digits.resize(digits.len() + trailing_zeros, b'0');
}
}
}
fn format_exponential_be_ascii_digits(
digits: String,
mut exp: i128,
sign: Sign,
f: &mut fmt::Formatter,
e_symbol: &str,
) -> fmt::Result {
let mut digits = digits.into_bytes();
let mut extra_trailing_zero_count = 0;
if let Some(prec) = f.precision() {
let total_prec = prec + 1;
let digit_count = digits.len();
match total_prec.cmp(&digit_count) {
Ordering::Equal => {
}
Ordering::Less => {
apply_rounding_to_ascii_digits(&mut digits, &mut exp, sign, total_prec);
}
Ordering::Greater => {
extra_trailing_zero_count = total_prec - digit_count;
}
}
}
let needs_decimal_point = digits.len() > 1 || extra_trailing_zero_count > 0;
let mut abs_int = String::from_utf8(digits).unwrap();
let exponent = abs_int.len() as i128 + exp - 1;
if needs_decimal_point {
abs_int.insert(1, '.');
}
if extra_trailing_zero_count > 0 {
abs_int.extend(core::iter::repeat('0').take(extra_trailing_zero_count));
}
write!(abs_int, "{}{:+}", e_symbol, exponent)?;
let non_negative = matches!(sign, Sign::Plus);
f.pad_integral(non_negative, "", &abs_int)
}
fn apply_rounding_to_ascii_digits(
ascii_digits: &mut Vec<u8>,
exp: &mut i128,
sign: Sign,
prec: usize,
) {
if ascii_digits.len() < prec {
return;
}
*exp += (ascii_digits.len() - prec) as i128;
let trailing_zeros = ascii_digits[prec + 1..].iter().all(|&d| d == b'0');
let sig_digit = ascii_digits[prec - 1] - b'0';
let insig_digit = ascii_digits[prec] - b'0';
let rounded_digit = round_pair_digits(
(sig_digit, insig_digit),
sign,
RoundingMode::default(),
trailing_zeros,
);
ascii_digits.truncate(prec - 1);
if rounded_digit < 10 {
ascii_digits.push(rounded_digit + b'0');
return;
}
debug_assert_eq!(rounded_digit, 10);
ascii_digits.push(b'0');
let digits = ascii_digits.iter_mut().rev().skip(1);
for digit in digits {
if *digit < b'9' {
*digit += 1;
return;
}
debug_assert_eq!(*digit, b'9');
*digit = b'0';
}
ascii_digits[0] = b'1';
*exp += 1;
}