use std::cmp::Ordering;
use crate::decimal::Decimal;
use crate::dfp::{self, Decoded, Width};
use crate::float::{Category, Float, Format, Status};
impl Float {
pub(super) fn decimal_bits(self, width: Width) -> u128 {
let sign = if self.sign { dfp::sign_bit(width) } else { 0 };
match self.category {
Category::Zero | Category::Finite => {
dfp::encode(self.sign, self.significand, self.exponent, width)
}
Category::Infinite => dfp::infinity(self.sign, width),
Category::Nan => {
let signaling = if self.exponent != 0 { 1u128 << (width.bits() - 7) } else { 0 };
dfp::nan(width) | signaling | sign
}
}
}
pub(super) fn decimal_from_bits(format: Format, width: Width, bits: u128) -> Float {
match dfp::decode(bits, width) {
Decoded::Finite { sign, coefficient, exponent } => Float {
format,
category: if coefficient == 0 { Category::Zero } else { Category::Finite },
sign,
exponent,
significand: coefficient,
},
Decoded::Infinite { sign } => {
Float { format, category: Category::Infinite, sign, exponent: 0, significand: 0 }
}
Decoded::Nan { sign, signaling } => Float {
format,
category: Category::Nan,
sign,
exponent: i32::from(signaling),
significand: 0,
},
}
}
fn decimal_parsed(text: &str, format: Format, width: Width) -> (Float, Status) {
match dfp::parse(text, width) {
Ok((bits, status)) => (Float::decimal_from_bits(format, width, bits), status),
Err(_) => (Float::nan(format), Status::INVALID),
}
}
fn decimal_text(self) -> String {
let sign = if self.sign { "-" } else { "" };
format!("{sign}{}e{}", self.significand, self.exponent)
}
pub(super) fn decimal_from_integer(
negative: bool,
magnitude: u128,
format: Format,
width: Width,
) -> (Float, Status) {
let sign = if negative { "-" } else { "" };
Float::decimal_parsed(&format!("{sign}{magnitude}"), format, width)
}
pub(super) fn decimal_to_format(self, format: Format) -> (Float, Status) {
match self.category {
Category::Nan => {
let nan = Float::nan(format);
return (Float { sign: self.sign, ..nan }, Status::NONE);
}
Category::Infinite => return (Float::infinity(format, self.sign), Status::NONE),
Category::Zero | Category::Finite => {}
}
let text = if self.format.decimal().is_some() {
if self.is_zero() && format.decimal().is_none() {
return (Float::zero(format, self.sign), Status::NONE);
}
self.decimal_text()
} else if self.is_zero() {
let sign = if self.sign { "-" } else { "" };
format!("{sign}0")
} else {
self.binary_text()
};
match format.decimal() {
Some(width) => Float::decimal_parsed(&text, format, width),
None => Float::parse(&text, format).unwrap_or((Float::nan(format), Status::INVALID)),
}
}
fn binary_text(self) -> String {
let (significand, exponent) = self.parts();
let digits: Vec<u8> = significand.to_string().bytes().map(|b| b - b'0').collect();
let point = digits.len() as i32;
let mut exact = Decimal::new(digits, point);
exact.shift(exponent);
let sign = if self.sign { "-" } else { "" };
format!("{sign}{}", exact.spelled())
}
pub(super) fn decimal_compare(self, other: Float) -> Option<Ordering> {
if self.is_nan() || other.is_nan() {
return None;
}
if self.is_zero() && other.is_zero() {
return Some(Ordering::Equal);
}
if self.sign != other.sign {
return Some(if self.sign { Ordering::Less } else { Ordering::Greater });
}
let magnitudes = match (self.category, other.category) {
(Category::Infinite, Category::Infinite) => Ordering::Equal,
(Category::Infinite, _) => Ordering::Greater,
(_, Category::Infinite) => Ordering::Less,
(Category::Zero, _) => Ordering::Less,
(_, Category::Zero) => Ordering::Greater,
_ => decimal_magnitudes(self, other),
};
Some(if self.sign { magnitudes.reverse() } else { magnitudes })
}
pub(super) fn decimal_integer(self, limit: u128) -> (i128, Status) {
match self.category {
Category::Nan => (0, Status::INVALID),
Category::Infinite => (self.signed_value(limit), Status::INVALID),
Category::Zero => (0, Status::NONE),
Category::Finite => {
let (magnitude, inexact) = if self.exponent >= 0 {
let scaled = u32::try_from(self.exponent)
.ok()
.and_then(|power| 10u128.checked_pow(power))
.and_then(|scale| self.significand.checked_mul(scale));
match scaled {
Some(magnitude) => (magnitude, false),
None => return (self.signed_value(limit), Status::INVALID),
}
} else {
let power = u32::try_from(-i64::from(self.exponent)).unwrap_or(u32::MAX);
match 10u128.checked_pow(power) {
Some(scale) => (self.significand / scale, self.significand % scale != 0),
None => (0, true),
}
};
if magnitude > limit {
return (self.signed_value(limit), Status::INVALID);
}
let status = if inexact { Status::INEXACT } else { Status::NONE };
(self.signed_value(magnitude), status)
}
}
}
pub(super) fn decimal_spelling(self) -> String {
let sign = if self.sign { "-" } else { "" };
match self.category {
Category::Nan => format!("{sign}nan"),
Category::Infinite => format!("{sign}inf"),
Category::Zero | Category::Finite => self.decimal_text(),
}
}
pub(super) fn decimal_is_normal(self, width: Width) -> bool {
let Category::Finite = self.category else { return false };
let adjusted = self.exponent + digits(self.significand) as i32 - 1;
adjusted >= width.min_exponent() + width.digits() as i32 - 1
}
}
fn digits(mut value: u128) -> u32 {
let mut count = 1;
while value >= 10 {
value /= 10;
count += 1;
}
count
}
fn decimal_magnitudes(left: Float, right: Float) -> Ordering {
let left_digits = digits(left.significand) as i32;
let right_digits = digits(right.significand) as i32;
let left_adjusted = left.exponent + left_digits;
let right_adjusted = right.exponent + right_digits;
if left_adjusted != right_adjusted {
return left_adjusted.cmp(&right_adjusted);
}
let (mut a, mut b) = (left.significand, right.significand);
match left_digits.cmp(&right_digits) {
Ordering::Less => a *= 10u128.pow((right_digits - left_digits) as u32),
Ordering::Greater => b *= 10u128.pow((left_digits - right_digits) as u32),
Ordering::Equal => {}
}
a.cmp(&b)
}