use crate::natural::Natural;
use crate::natural::arithmetic::log_base::log_base_helper_with_pow;
use crate::natural::conversion::string::to_string::BaseFmtWrapper;
use crate::natural::slice_trailing_zeros;
use alloc::string::String;
use core::fmt::{Display, Formatter, Write};
use malachite_base::num::arithmetic::traits::{
CheckedLogBase2, DivExact, DivRound, DivisibleBy, DivisibleByPowerOf2, FloorLogBase,
FloorLogBasePowerOf2, Pow, ShrRound,
};
use malachite_base::num::basic::traits::Zero;
use malachite_base::num::conversion::string::options::{SciSizeOptions, ToSciOptions};
use malachite_base::num::conversion::string::to_sci::write_exponent;
use malachite_base::num::conversion::string::to_string::{
digit_to_display_byte_lower, digit_to_display_byte_upper,
};
use malachite_base::num::conversion::traits::{Digits, ExactFrom, ToSci};
use malachite_base::rounding_modes::RoundingMode::*;
fn write_helper<T>(x: &T, f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result
where
for<'a> BaseFmtWrapper<&'a T>: Display,
{
let w = BaseFmtWrapper {
x,
base: options.get_base(),
};
if options.get_lowercase() {
Display::fmt(&w, f)
} else {
write!(f, "{w:#}")
}
}
impl ToSci for Natural {
fn fmt_sci_valid(&self, options: ToSciOptions) -> bool {
if *self == 0u32 || options.get_rounding_mode() != Exact {
return true;
}
match options.get_size_options() {
SciSizeOptions::Complete | SciSizeOptions::Scale(_) => true,
SciSizeOptions::Precision(precision) => {
let n_base = Self::from(options.get_base());
let log = self.floor_log_base(&n_base);
if log < precision {
return true;
}
let scale = log - precision + 1;
if let Some(base_log) = options.get_base().checked_log_base_2() {
self.divisible_by_power_of_2(base_log * scale)
} else {
self.divisible_by(n_base.pow(scale))
}
}
}
}
fn fmt_sci(&self, f: &mut Formatter, options: ToSciOptions) -> core::fmt::Result {
match options.get_size_options() {
SciSizeOptions::Complete | SciSizeOptions::Scale(0) => write_helper(self, f, options),
SciSizeOptions::Scale(scale) => {
write_helper(self, f, options)?;
if options.get_include_trailing_zeros() {
f.write_char('.')?;
for _ in 0..scale {
f.write_char('0')?;
}
}
Ok(())
}
SciSizeOptions::Precision(precision) => {
let n_base = Self::from(options.get_base());
let (base_log, log, power, p) = if *self == 0u32 {
(None, 0, Self::ZERO, 0)
} else if let Some(base_log) = options.get_base().checked_log_base_2() {
(
Some(base_log),
self.floor_log_base_power_of_2(base_log),
Self::ZERO,
0,
)
} else {
let (log, _, power, p) = log_base_helper_with_pow(self, &n_base);
(None, log, power, p)
};
if log < precision {
write_helper(self, f, options)?;
if options.get_include_trailing_zeros() {
let extra_zeros = precision - log - 1;
if extra_zeros != 0 {
f.write_char('.')?;
for _ in 0..extra_zeros {
f.write_char('0')?;
}
}
}
Ok(())
} else {
let mut e = log;
let scale = log - precision + 1;
let shifted = if let Some(base_log) = base_log {
self.shr_round(base_log * scale, options.get_rounding_mode())
.0
} else {
let n = if precision > log >> 1 {
n_base.pow(scale)
} else if p >= scale {
power.div_exact(n_base.pow(p - scale))
} else {
assert!(p == scale + 1);
power * n_base
};
self.div_round(n, options.get_rounding_mode()).0
};
let mut chars = shifted.to_digits_desc(&options.get_base());
let mut len = chars.len();
let p = usize::exact_from(precision);
if len > p {
assert_eq!(chars.pop().unwrap(), 0);
len -= 1;
e += 1;
}
assert_eq!(len, p);
if !options.get_include_trailing_zeros() {
chars.truncate(len - slice_trailing_zeros(&chars));
}
if options.get_lowercase() {
for digit in &mut chars {
*digit = digit_to_display_byte_lower(*digit).unwrap();
}
} else {
for digit in &mut chars {
*digit = digit_to_display_byte_upper(*digit).unwrap();
}
}
len = chars.len();
if len != 1 {
chars.push(b'0');
chars.copy_within(1..len, 2);
chars[1] = b'.';
}
f.write_str(&String::from_utf8(chars).unwrap())?;
write_exponent(f, options, e)
}
}
}
}
}