use crate::Float;
use crate::InnerFloat::{Finite, Infinity, NaN, Zero};
use crate::float::conversion::string::format_float::strip_trailing_zeros;
use crate::float::conversion::string::get_str::get_str;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering::*;
use core::fmt::Write;
use malachite_base::num::arithmetic::traits::{Abs, DivRound, Pow};
use malachite_base::num::conversion::string::options::{SciSizeOptions, ToSciOptions};
use malachite_base::num::conversion::traits::{ExactFrom, IntegerMantissaAndExponent};
use malachite_base::rounding_modes::RoundingMode::*;
use malachite_q::Rational;
fn length_after_point(k: i64, base: i64) -> Option<u64> {
if k >= 0 {
Some(0)
} else {
match u64::from(base.trailing_zeros()) {
0 => None,
v => Some(k.unsigned_abs().div_round(v, Ceiling).0),
}
}
}
fn floor_log_base(x: &Float, base: i64) -> i64 {
get_str(x, base, 1, Down).unwrap().1 - 1
}
fn push_exponent(out: &mut String, options: ToSciOptions, exp: i64) {
out.push(if options.get_e_lowercase() { 'e' } else { 'E' });
if exp > 0 && (options.get_force_exponent_plus_sign() || options.get_base() >= 15) {
out.push('+');
}
write!(out, "{exp}").unwrap();
}
fn zero_to_string(neg: bool, options: ToSciOptions) -> String {
let mut out = String::new();
if neg {
out.push('-');
}
out.push('0');
if options.get_include_trailing_zeros() {
let zeros = match options.get_size_options() {
SciSizeOptions::Complete => 0,
SciSizeOptions::Scale(scale) => scale,
SciSizeOptions::Precision(precision) => precision - 1,
};
if zeros != 0 {
out.push('.');
for _ in 0..zeros {
out.push('0');
}
}
}
if !out.contains('.') {
out.push_str(".0");
}
out
}
pub_crate_test! {
to_sci_valid(x: &Float, options: ToSciOptions) -> bool {
if !matches!(x, Float(Finite { .. })) {
return true;
}
let base = i64::from(options.get_base());
let min_scale = length_after_point(x.integer_exponent(), base);
if let SciSizeOptions::Complete = options.get_size_options() {
return min_scale.is_some();
}
if options.get_rounding_mode() != Exact {
return true;
}
let Some(min_scale) = min_scale else {
return false;
};
let min_scale = i64::exact_from(min_scale);
match options.get_size_options() {
SciSizeOptions::Scale(scale) => min_scale <= i64::exact_from(scale),
SciSizeOptions::Precision(precision) => {
min_scale <= i64::exact_from(precision - 1) - floor_log_base(x, base)
}
SciSizeOptions::Complete => unreachable!(),
}
}}
pub_crate_test! {
to_sci_string(x: &Float, options: ToSciOptions) -> String {
let (neg, sign) = match x {
Float(NaN) => return String::from("NaN"),
Float(Infinity { sign: true }) => return String::from("Infinity"),
Float(Infinity { sign: false }) => return String::from("-Infinity"),
Float(Zero { sign }) => return zero_to_string(!*sign, options),
Float(Finite { sign, .. }) => (!*sign, *sign),
};
let base = i64::from(options.get_base());
let rm = options.get_rounding_mode();
let trim_zeros = !options.get_include_trailing_zeros()
&& options.get_size_options() != SciSizeOptions::Complete;
let log = floor_log_base(x, base);
let (scale, precision) = match options.get_size_options() {
SciSizeOptions::Complete => {
let scale = length_after_point(x.integer_exponent(), base).unwrap_or_else(|| {
panic!("{x} has a non-terminating expansion in base {base}")
});
let precision = i64::exact_from(scale) + log + 1;
assert!(precision > 0);
(i64::exact_from(scale), precision)
}
SciSizeOptions::Scale(scale) => {
(i64::exact_from(scale), i64::exact_from(scale) + log + 1)
}
SciSizeOptions::Precision(precision) => (
i64::exact_from(precision - 1) - log,
i64::exact_from(precision),
),
};
let (digits, log) = if precision <= 0 {
let round_up_to_one = match rm {
Up => true,
Down => false,
Floor => neg,
Ceiling => !neg,
Exact => panic!(
"Exact rounding was requested, but {x} is not exactly representable with {scale} \
digits after the point",
),
Nearest => {
log + 1 == -scale && {
let two_x = Rational::exact_from(x).abs() << 1u32;
two_x > Rational::from(base).pow(-scale)
}
}
};
if round_up_to_one {
(vec![b'1'], -scale)
} else {
return zero_to_string(neg, options);
}
} else {
let m = usize::exact_from(precision);
let get_str_base = if options.get_lowercase() { base } else { -base };
let (s, e, o) = get_str(x, get_str_base, m, rm).unwrap();
let mut digits = if neg { s[1..].to_vec() } else { s };
debug_assert!(options.get_size_options() != SciSizeOptions::Complete || o == Equal);
let new_log = e - 1;
if new_log > log && matches!(options.get_size_options(), SciSizeOptions::Scale(_)) {
digits.push(b'0');
}
(digits, new_log)
};
let target_scale = match options.get_size_options() {
SciSizeOptions::Precision(_) => i64::exact_from(digits.len()) - 1 - log,
_ => scale,
};
let mut mantissa: Vec<u8> = Vec::new();
let mut exponent = None;
if log <= options.get_neg_exp_threshold() || target_scale < 0 {
let ds = if trim_zeros {
strip_trailing_zeros(&digits)
} else {
&digits
};
mantissa.push(ds[0]);
if ds.len() > 1 {
mantissa.push(b'.');
mantissa.extend_from_slice(&ds[1..]);
}
exponent = Some(log);
} else if log < 0 {
let ds = if trim_zeros {
strip_trailing_zeros(&digits)
} else {
&digits
};
mantissa.extend_from_slice(b"0.");
mantissa.resize(2 + usize::exact_from(-log - 1), b'0');
mantissa.extend_from_slice(ds);
debug_assert!(
trim_zeros || -log - 1 + i64::exact_from(ds.len()) == target_scale,
"fractional length mismatch"
);
} else {
let digits_before = usize::exact_from(log + 1);
mantissa.extend_from_slice(&digits[..digits_before]);
let frac = if trim_zeros {
strip_trailing_zeros(&digits[digits_before..])
} else {
&digits[digits_before..]
};
if !frac.is_empty() {
mantissa.push(b'.');
mantissa.extend_from_slice(frac);
}
debug_assert!(
trim_zeros || i64::exact_from(frac.len()) == target_scale,
"fractional length mismatch"
);
}
if !mantissa.contains(&b'.') {
mantissa.extend_from_slice(b".0");
}
let mut out = String::new();
if !sign {
out.push('-');
}
out.push_str(core::str::from_utf8(&mantissa).unwrap());
if let Some(exp) = exponent {
push_exponent(&mut out, options, exp);
}
out
}}