use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use crate::bigint::BigUint;
use crate::float::Float128;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseFloat128Error {
Empty,
Unexpected {
found: char,
at: usize,
},
BadExponent,
}
impl fmt::Display for ParseFloat128Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "a binary128 value needs at least one digit"),
Self::Unexpected { found, at } => {
write!(
f,
"`{found}` cannot appear at byte {at} of a binary128 value"
)
}
Self::BadExponent => write!(f, "the exponent is not an integer this crate can use"),
}
}
}
impl core::error::Error for ParseFloat128Error {}
const DECIMAL_LIMIT: i64 = 6000;
impl Float128 {
#[must_use]
pub fn to_exact_decimal(&self) -> String {
match self.exact_digits() {
None => non_finite(*self).to_string(),
Some((negative, digits, point)) => render(negative, &digits, point),
}
}
#[must_use]
pub fn to_shortest_decimal(&self) -> String {
let Some((negative, digits, point)) = self.exact_digits() else {
return non_finite(*self).to_string();
};
if digits.is_empty() {
return render(negative, &digits, point);
}
for length in 1..=Self::DIGITS_ROUND_TRIP {
if length >= digits.len() {
break;
}
let (rounded, rounded_point) = round_significant(&digits, point, length);
let text = render(negative, &rounded, rounded_point);
if Self::from_str(&text).is_ok_and(|back| back.to_bits() == self.to_bits()) {
return text;
}
}
render(negative, &digits, point)
}
fn exact_digits(&self) -> Option<(bool, Vec<u8>, i32)> {
let (negative, significand, exponent) = self.decompose()?;
if significand == 0 {
return Some((negative, Vec::new(), 0));
}
let mut value = BigUint::from_u128(significand);
let point;
if exponent >= 0 {
value.shl(exponent as usize);
let mut digits = value.to_decimal_digits();
point = decimal_point(digits.len(), 0);
trim_trailing_zeros(&mut digits);
return Some((negative, digits, point));
}
let scale = (-exponent) as usize;
value.mul_pow5(scale);
let mut digits = value.to_decimal_digits();
point = decimal_point(digits.len(), scale);
trim_trailing_zeros(&mut digits);
Some((negative, digits, point))
}
}
fn decimal_point(digits: usize, scale: usize) -> i32 {
let digits = i32::try_from(digits).unwrap_or(i32::MAX);
let scale = i32::try_from(scale).unwrap_or(i32::MAX);
digits.saturating_sub(scale)
}
fn trim_trailing_zeros(digits: &mut Vec<u8>) {
while digits.last() == Some(&0) {
digits.pop();
}
}
fn non_finite(value: Float128) -> &'static str {
if value.is_nan() {
"nan"
} else if value.is_sign_negative() {
"-inf"
} else {
"inf"
}
}
fn round_significant(digits: &[u8], point: i32, length: usize) -> (Vec<u8>, i32) {
let mut kept: Vec<u8> = digits[..length].to_vec();
let next = digits[length];
let rest_nonzero = digits[length + 1..].iter().any(|&digit| digit != 0);
let round_up = match next.cmp(&5) {
core::cmp::Ordering::Greater => true,
core::cmp::Ordering::Less => false,
core::cmp::Ordering::Equal => rest_nonzero || kept[length - 1] % 2 == 1,
};
if !round_up {
while kept.last() == Some(&0) {
kept.pop();
}
return (kept, point);
}
let mut index = length;
loop {
if index == 0 {
kept.insert(0, 1);
kept.truncate(length);
while kept.last() == Some(&0) {
kept.pop();
}
return (kept, point + 1);
}
index -= 1;
if kept[index] == 9 {
kept[index] = 0;
} else {
kept[index] += 1;
break;
}
}
while kept.last() == Some(&0) {
kept.pop();
}
(kept, point)
}
fn render(negative: bool, digits: &[u8], point: i32) -> String {
let mut out = String::new();
if negative {
out.push('-');
}
if digits.is_empty() {
out.push_str("0.0");
return out;
}
let length = decimal_point(digits.len(), 0);
if point > 0 && point <= 34 {
for (index, &digit) in digits.iter().enumerate() {
if decimal_point(index, 0) == point {
out.push('.');
}
out.push(char::from(b'0' + digit));
}
if length <= point {
for _ in length..point {
out.push('0');
}
out.push_str(".0");
}
return out;
}
if point <= 0 && point > -6 {
out.push_str("0.");
for _ in 0..-point {
out.push('0');
}
for &digit in digits {
out.push(char::from(b'0' + digit));
}
return out;
}
out.push(char::from(b'0' + digits[0]));
out.push('.');
if digits.len() == 1 {
out.push('0');
} else {
for &digit in &digits[1..] {
out.push(char::from(b'0' + digit));
}
}
out.push('e');
let exponent = point - 1;
if exponent >= 0 {
out.push('+');
}
out.push_str(&exponent.to_string());
out
}
impl fmt::Display for Float128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(&self.to_shortest_decimal())
}
}
impl FromStr for Float128 {
type Err = ParseFloat128Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
parse(text)
}
}
fn parse(text: &str) -> Result<Float128, ParseFloat128Error> {
let trimmed = text.trim();
let (negative, body) = match trimmed.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, trimmed.strip_prefix('+').unwrap_or(trimmed)),
};
if body.is_empty() {
return Err(ParseFloat128Error::Empty);
}
if body.eq_ignore_ascii_case("inf") || body.eq_ignore_ascii_case("infinity") {
return Ok(if negative {
Float128::NEG_INFINITY
} else {
Float128::INFINITY
});
}
if body.eq_ignore_ascii_case("nan") {
let nan = Float128::NAN;
return Ok(if negative { nan.neg() } else { nan });
}
let (mantissa, exponent_text) = match body.find(['e', 'E']) {
Some(index) => (&body[..index], Some(&body[index + 1..])),
None => (body, None),
};
let mut exponent: i64 = match exponent_text {
None => 0,
Some(digits) => digits
.parse()
.map_err(|_| ParseFloat128Error::BadExponent)?,
};
let mut significand = BigUint::zero();
let mut seen_digit = false;
let mut seen_point = false;
let mut fraction_digits: i64 = 0;
for (offset, character) in mantissa.char_indices() {
match character {
'0'..='9' => {
seen_digit = true;
significand.mul_add_small(10, u32::from(character as u8 - b'0'));
if seen_point {
fraction_digits += 1;
}
}
'.' if !seen_point => seen_point = true,
other => {
return Err(ParseFloat128Error::Unexpected {
found: other,
at: offset,
});
}
}
}
if !seen_digit {
return Err(ParseFloat128Error::Empty);
}
exponent -= fraction_digits;
if significand.is_zero() {
return Ok(if negative {
Float128::NEG_ZERO
} else {
Float128::ZERO
});
}
let bits = significand.bit_length() as i64;
let digits_low = bits * 30103 / 100_000;
let digits_high = digits_low + 2;
if exponent + digits_low > DECIMAL_LIMIT {
return Ok(if negative {
Float128::NEG_INFINITY
} else {
Float128::INFINITY
});
}
if exponent + digits_high < -DECIMAL_LIMIT {
return Ok(if negative {
Float128::NEG_ZERO
} else {
Float128::ZERO
});
}
if exponent >= 0 {
let mut scaled = significand;
scaled.mul_pow10(exponent.unsigned_abs() as usize);
let (value, shift) = significand_with_sticky(scaled);
return Ok(Float128::from_parts_rounded(negative, value, shift).0);
}
let scale = exponent.unsigned_abs() as usize;
let wanted = 130 + scale * 10 / 3;
let have = significand.bit_length();
let shift = wanted.saturating_sub(have);
let mut numerator = significand;
numerator.shl(shift);
let sticky = divide_by_pow10(&mut numerator, scale);
let (mut value, extra) = significand_with_sticky(numerator);
if sticky {
value |= 1;
}
let exponent = extra - decimal_point(shift, 0);
Ok(Float128::from_parts_rounded(negative, value, exponent).0)
}
fn divide_by_pow10(value: &mut BigUint, power: usize) -> bool {
const CHUNK: u32 = 1_000_000_000;
const CHUNK_POWER: usize = 9;
let mut sticky = false;
let mut left = power;
while left >= CHUNK_POWER {
sticky |= value.div_rem_small(CHUNK) != 0;
left -= CHUNK_POWER;
}
if left != 0 {
let mut divisor = 1u32;
for _ in 0..left {
divisor *= 10;
}
sticky |= value.div_rem_small(divisor) != 0;
}
sticky
}
fn significand_with_sticky(value: BigUint) -> (u128, i32) {
let bits = value.bit_length();
if bits <= 127 {
return (value.to_u128().0, 0);
}
let drop = bits - 118;
let mut kept = value;
let lost = kept.shr(drop);
let (mut low, _) = kept.to_u128();
if lost {
low |= 1;
}
(low, decimal_point(drop, 0))
}