use core::cmp::Ordering;
use oxinum_core::{OxiNumError, OxiNumResult, Sign};
use oxinum_int::native::BigUint;
use super::float::{BigFloat, FloatClass, RoundingMode};
fn pow10(n: u64) -> BigUint {
const CHUNK_EXP: u64 = 19;
const CHUNK_VAL: u64 = 10_000_000_000_000_000_000; let mut acc = BigUint::one();
let full = n / CHUNK_EXP;
let rem = n % CHUNK_EXP;
let chunk = BigUint::from_u64(CHUNK_VAL);
for _ in 0..full {
acc = &acc * &chunk;
}
if rem > 0 {
let mut tail: u64 = 1;
for _ in 0..rem {
tail *= 10;
}
acc = &acc * &BigUint::from_u64(tail);
}
acc
}
fn pow2(n: u64) -> BigUint {
BigUint::one().shl_bits(n)
}
fn round_ratio_half_even(num: &BigUint, den: &BigUint) -> BigUint {
let quotient = num / den;
let remainder = num % den;
if remainder.is_zero() {
return quotient;
}
let twice_rem = remainder.shl_bits(1);
match twice_rem.cmp(den) {
Ordering::Less => quotient,
Ordering::Greater => "ient + &BigUint::one(),
Ordering::Equal => {
if quotient.test_bit(0) {
"ient + &BigUint::one()
} else {
quotient
}
}
}
}
fn decimal_magnitude(value: &BigFloat, sig_digits: usize) -> (String, i64) {
let d = sig_digits.max(1);
let m = value.mantissa();
let e = value.exponent();
let top_bit = e.saturating_add(m.bit_length() as i64 - 1);
let mut big_e = (top_bit as f64 * core::f64::consts::LOG10_2).floor() as i64;
let cmp_vs_pow10 = |cand: i64| -> Ordering {
let e_pos = e.max(0) as u64;
let e_neg = (-e).max(0) as u64;
let c_pos = cand.max(0) as u64;
let c_neg = (-cand).max(0) as u64;
let lhs = {
let mut x = m.shl_bits(e_pos);
if c_neg > 0 {
x = &x * &pow10(c_neg);
}
x
};
let rhs = {
let mut x = pow2(e_neg);
if c_pos > 0 {
x = &x * &pow10(c_pos);
}
x
};
lhs.cmp(&rhs)
};
while cmp_vs_pow10(big_e + 1) != Ordering::Less {
big_e += 1;
}
while cmp_vs_pow10(big_e) == Ordering::Less {
big_e -= 1;
}
let k = big_e - (d as i64) + 1; let e_pos = e.max(0) as u64;
let e_neg = (-e).max(0) as u64;
let k_pos = k.max(0) as u64;
let k_neg = (-k).max(0) as u64;
let num = {
let mut x = m.shl_bits(e_pos);
if k_neg > 0 {
x = &x * &pow10(k_neg);
}
x
};
let den = {
let mut x = pow2(e_neg);
if k_pos > 0 {
x = &x * &pow10(k_pos);
}
x
};
let n = round_ratio_half_even(&num, &den);
let mut digits = match n.to_radix(10) {
Ok(s) => s,
Err(_) => "0".repeat(d),
};
let mut exp10 = big_e;
if digits.len() == d + 1 {
digits.pop();
exp10 += 1;
}
while digits.len() < d {
digits.push('0');
}
if digits.len() > d {
digits.truncate(d);
}
(digits, exp10)
}
impl BigFloat {
pub fn to_scientific_string(&self, sig_digits: usize) -> String {
match self.class {
FloatClass::Nan => return "NaN".to_string(),
FloatClass::Infinite => {
return if self.sign() == Sign::Negative {
"-inf".to_string()
} else {
"inf".to_string()
};
}
FloatClass::Finite => {}
}
let d = sig_digits.max(1);
if self.is_zero() {
if d == 1 {
return "0e0".to_string();
}
let mut s = String::from("0.");
for _ in 1..d {
s.push('0');
}
s.push_str("e0");
return s;
}
let (digits, exp10) = decimal_magnitude(self, d);
let mut out = String::new();
if self.sign() == Sign::Negative {
out.push('-');
}
out.push_str(&digits[..1]);
if digits.len() > 1 {
out.push('.');
out.push_str(&digits[1..]);
}
out.push('e');
out.push_str(&exp10.to_string());
out
}
pub fn to_engineering_string(&self, sig_digits: usize) -> String {
match self.class {
FloatClass::Nan => return "NaN".to_string(),
FloatClass::Infinite => {
return if self.sign() == Sign::Negative {
"-inf".to_string()
} else {
"inf".to_string()
};
}
FloatClass::Finite => {}
}
let d = sig_digits.max(1);
if self.is_zero() {
if d == 1 {
return "0e0".to_string();
}
let mut s = String::from("0.");
for _ in 1..d {
s.push('0');
}
s.push_str("e0");
return s;
}
let (digits, exp10) = decimal_magnitude(self, d);
let shift = exp10.rem_euclid(3); let eng_exp = exp10 - shift;
let int_digits = (shift + 1) as usize;
let mut padded = digits;
while padded.len() < int_digits {
padded.push('0');
}
let mut out = String::new();
if self.sign() == Sign::Negative {
out.push('-');
}
out.push_str(&padded[..int_digits]);
if padded.len() > int_digits {
out.push('.');
out.push_str(&padded[int_digits..]);
}
out.push('e');
out.push_str(&eng_exp.to_string());
out
}
}
fn hex_value(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
fn hex_char(v: u8) -> char {
match v {
0..=9 => (b'0' + v) as char,
10..=15 => (b'a' + (v - 10)) as char,
_ => '0',
}
}
impl BigFloat {
pub fn to_hex_string(&self) -> String {
match self.class {
FloatClass::Nan => return "NaN".to_string(),
FloatClass::Infinite => {
return if self.sign() == Sign::Negative {
"-inf".to_string()
} else {
"inf".to_string()
};
}
FloatClass::Finite => {}
}
if self.is_zero() {
return "0x0p0".to_string();
}
let m = self.mantissa();
let bits = m.bit_length(); let leading_index = bits - 1;
let p_exp = self.exponent().saturating_add(leading_index as i64);
let mut out = String::new();
if self.sign() == Sign::Negative {
out.push('-');
}
out.push_str("0x1");
if leading_index > 0 {
let mut frac = String::new();
let mut pos = leading_index as i64 - 1;
while pos >= 0 {
let mut nibble: u8 = 0;
for _ in 0..4 {
nibble <<= 1;
if pos >= 0 {
if m.test_bit(pos as u64) {
nibble |= 1;
}
pos -= 1;
}
}
frac.push(hex_char(nibble));
}
while frac.ends_with('0') {
frac.pop();
}
if !frac.is_empty() {
out.push('.');
out.push_str(&frac);
}
}
out.push('p');
out.push_str(&p_exp.to_string());
out
}
pub fn from_hex_float(s: &str, prec: u32) -> OxiNumResult<Self> {
let bytes = s.as_bytes();
let mut idx = 0usize;
let len = bytes.len();
let parse_err = |msg: &str| OxiNumError::Parse(format!("hex float: {msg}").into());
let sign = match bytes.first() {
Some(b'-') => {
idx += 1;
Sign::Negative
}
Some(b'+') => {
idx += 1;
Sign::Positive
}
_ => Sign::Positive,
};
if idx + 1 >= len || bytes[idx] != b'0' || (bytes[idx + 1] | 0x20) != b'x' {
return Err(parse_err("missing '0x' prefix"));
}
idx += 2;
let int_start = idx;
while idx < len && hex_value(bytes[idx]).is_some() {
idx += 1;
}
let int_part = &bytes[int_start..idx];
let mut frac_part: &[u8] = &[];
if idx < len && bytes[idx] == b'.' {
idx += 1;
let frac_start = idx;
while idx < len && hex_value(bytes[idx]).is_some() {
idx += 1;
}
frac_part = &bytes[frac_start..idx];
}
if int_part.is_empty() && frac_part.is_empty() {
return Err(parse_err("no significand digits"));
}
if idx >= len || (bytes[idx] | 0x20) != b'p' {
return Err(parse_err("missing 'p' exponent marker"));
}
idx += 1;
let exp_sign_neg = match bytes.get(idx) {
Some(b'-') => {
idx += 1;
true
}
Some(b'+') => {
idx += 1;
false
}
_ => false,
};
let exp_start = idx;
while idx < len && bytes[idx].is_ascii_digit() {
idx += 1;
}
if exp_start == idx {
return Err(parse_err("missing exponent digits"));
}
if idx != len {
return Err(parse_err("trailing characters"));
}
let exp_str = core::str::from_utf8(&bytes[exp_start..idx])
.map_err(|_| parse_err("non-UTF-8 exponent"))?;
let exp_mag: i64 = exp_str
.parse::<i64>()
.map_err(|_| parse_err("exponent out of range"))?;
let p_exp = if exp_sign_neg { -exp_mag } else { exp_mag };
let frac_nibbles = frac_part.len() as i64;
let mut all_digits: Vec<u8> = Vec::with_capacity(int_part.len() + frac_part.len());
all_digits.extend_from_slice(int_part);
all_digits.extend_from_slice(frac_part);
let mut mantissa = BigUint::zero();
let sixteen = BigUint::from_u64(16);
for &c in &all_digits {
let v = hex_value(c).ok_or_else(|| parse_err("invalid hex digit"))?;
mantissa = &(&mantissa * &sixteen) + &BigUint::from_u64(v as u64);
}
if mantissa.is_zero() {
return Ok(Self::zero(prec));
}
let exponent = p_exp.saturating_sub(frac_nibbles.saturating_mul(4));
Ok(Self::from_parts(
sign,
mantissa,
exponent,
prec,
RoundingMode::HalfEven,
))
}
}