const FIXED_MIN_DECPT: i32 = -4;
const FIXED_MAX_DECPT: i32 = 16;
fn double_to_string(value: f64, force_sign: bool) -> String {
let plus = if force_sign && !value.is_sign_negative() { "+" } else { "" };
if value.is_nan() {
return format!("{}nan", if force_sign { "+" } else { "" });
}
if value.is_infinite() {
return format!("{plus}{}", if value.is_sign_negative() { "-inf" } else { "inf" });
}
let scientific = format!("{value:e}");
let (mantissa, exponent) = scientific.split_once('e').unwrap_or((scientific.as_str(), "0"));
let negative = mantissa.starts_with('-');
let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
let exponent: i32 = exponent.parse().unwrap_or(0);
let decpt = exponent + 1;
let sign = if negative { "-" } else { plus };
if decpt <= FIXED_MIN_DECPT || decpt > FIXED_MAX_DECPT {
let mut out = String::from(sign);
out.push_str(&digits[..1]);
if digits.len() > 1 {
out.push('.');
out.push_str(&digits[1..]);
}
let exp = decpt - 1;
out.push('e');
out.push(if exp < 0 { '-' } else { '+' });
out.push_str(&format!("{:02}", exp.abs()));
return out;
}
let mut out = String::from(sign);
if decpt <= 0 {
out.push_str("0.");
for _ in 0..-decpt {
out.push('0');
}
out.push_str(&digits);
} else if decpt as usize >= digits.len() {
out.push_str(&digits);
for _ in 0..(decpt as usize - digits.len()) {
out.push('0');
}
} else {
out.push_str(&digits[..decpt as usize]);
out.push('.');
out.push_str(&digits[decpt as usize..]);
}
out
}
pub fn repr_f64(value: f64) -> String {
let s = double_to_string(value, false);
if s.contains('.') || s.contains('e') || s.contains("nan") || s.contains("inf") {
s
} else {
format!("{s}.0")
}
}
pub fn repr_complex(real: f64, imaginary: f64) -> String {
if real == 0.0 && real.is_sign_positive() {
return format!("{}j", double_to_string(imaginary, false));
}
format!("({}{}j)", double_to_string(real, false), double_to_string(imaginary, true))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn floats_match_python_repr() {
assert_eq!(repr_f64(0.0), "0.0");
assert_eq!(repr_f64(-0.0), "-0.0");
assert_eq!(repr_f64(1.0), "1.0");
assert_eq!(repr_f64(-1.5), "-1.5");
assert_eq!(repr_f64(0.1), "0.1");
assert_eq!(repr_f64(1e15), "1000000000000000.0");
assert_eq!(repr_f64(1e16), "1e+16");
assert_eq!(repr_f64(1e-4), "0.0001");
assert_eq!(repr_f64(1e-5), "1e-05");
assert_eq!(repr_f64(f64::MAX), "1.7976931348623157e+308");
assert_eq!(repr_f64(f64::MIN_POSITIVE), "2.2250738585072014e-308");
assert_eq!(repr_f64(f64::EPSILON), "2.220446049250313e-16");
assert_eq!(repr_f64(f64::INFINITY), "inf");
assert_eq!(repr_f64(f64::NEG_INFINITY), "-inf");
assert_eq!(repr_f64(f64::NAN), "nan");
}
#[test]
fn complex_matches_python_repr() {
assert_eq!(repr_complex(0.0, 0.0), "0j");
assert_eq!(repr_complex(-0.0, 0.0), "(-0+0j)");
assert_eq!(repr_complex(1.0, 2.0), "(1+2j)");
assert_eq!(repr_complex(1.0, -2.0), "(1-2j)");
assert_eq!(repr_complex(0.0, -1.0), "-1j");
assert_eq!(repr_complex(f64::NAN, f64::NAN), "(nan+nanj)");
assert_eq!(repr_complex(f64::NAN, f64::INFINITY), "(nan+infj)");
assert_eq!(repr_complex(f64::NAN, f64::NEG_INFINITY), "(nan-infj)");
assert_eq!(repr_complex(0.0, -f64::MAX), "-1.7976931348623157e+308j");
assert_eq!(repr_complex(0.0, f64::EPSILON), "2.220446049250313e-16j");
}
#[test]
fn the_shortest_form_still_round_trips() {
let values = [
0.1,
1.0 / 3.0,
f64::MAX,
f64::MIN_POSITIVE,
f64::EPSILON,
1e16,
1e-5,
-2.5e-300,
123456789012345678.0,
];
for v in values {
let text = repr_f64(v);
let back: f64 = text.parse().unwrap_or_else(|e| panic!("{text}: {e}"));
assert_eq!(back.to_bits(), v.to_bits(), "{v} -> {text}");
}
}
}