use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum XType {
Float,
Exp,
Generic,
}
struct FpDecode {
sign: u8, is_special: u8, n: usize, i_dp: i32, digits: Vec<u8>, }
fn dekker_mul2(x: &mut [f64; 2], y: f64, yy: f64) {
use core::hint::black_box as bb;
let mut m = x[0].to_bits();
m &= 0xfffffffffc000000u64;
let hx = f64::from_bits(m);
let tx = bb(x[0] - hx);
m = y.to_bits();
m &= 0xfffffffffc000000u64;
let hy = f64::from_bits(m);
let ty = bb(y - hy);
let p = bb(hx * hy);
let q = bb(bb(hx * ty) + bb(tx * hy));
let c = bb(p + q);
let mut cc = bb(bb(bb(p - c) + q) + bb(tx * ty));
cc = bb(bb(bb(x[0] * yy) + bb(x[1] * y)) + cc);
x[0] = c + cc;
x[1] = c - x[0];
x[1] += cc;
}
impl FpDecode {
#[allow(clippy::excessive_precision)]
fn decode(mut r: f64, mut i_round: i32, mx_round: i32) -> FpDecode {
let mut zbuf = [0u8; 24];
let mut exp: i32 = 0;
let sign;
if r < 0.0 {
sign = b'-';
r = -r;
} else if r == 0.0 {
return FpDecode {
sign: b'+',
is_special: 0,
n: 1,
i_dp: 1,
digits: alloc::vec![b'0'],
};
} else {
sign = b'+';
}
let v0 = r.to_bits();
let e = v0 >> 52;
if (e & 0x7ff) == 0x7ff {
return FpDecode {
sign,
is_special: if v0 != 0x7ff0000000000000 { 2 } else { 1 },
n: 0,
i_dp: 0,
digits: Vec::new(),
};
}
let mut rr = [r, 0.0f64];
if rr[0] > 9.223372036854774784e18 {
while rr[0] > 9.223372036854774784e118 {
exp += 100;
dekker_mul2(&mut rr, 1.0e-100, -1.99918998026028836196e-117);
}
while rr[0] > 9.223372036854774784e28 {
exp += 10;
dekker_mul2(&mut rr, 1.0e-10, -3.6432197315497741579e-27);
}
while rr[0] > 9.223372036854774784e18 {
exp += 1;
dekker_mul2(&mut rr, 1.0e-01, -5.5511151231257827021e-18);
}
} else {
while rr[0] < 9.223372036854774784e-83 {
exp -= 100;
dekker_mul2(&mut rr, 1.0e100, -1.5902891109759918046e83);
}
while rr[0] < 9.223372036854774784e7 {
exp -= 10;
dekker_mul2(&mut rr, 1.0e10, 0.0);
}
while rr[0] < 9.22337203685477478e17 {
exp -= 1;
dekker_mul2(&mut rr, 1.0e01, 0.0);
}
}
let mut v: u64 = if rr[1] < 0.0 {
(rr[0] as u64) - ((-rr[1]) as u64)
} else {
(rr[0] as u64) + (rr[1] as u64)
};
let mut i: i32 = zbuf.len() as i32 - 1;
while v != 0 {
zbuf[i as usize] = (v % 10) as u8 + b'0';
i -= 1;
v /= 10;
}
let mut n = zbuf.len() as i32 - 1 - i;
let mut i_dp = n + exp;
if i_round <= 0 {
i_round = i_dp - i_round;
if i_round == 0 && zbuf[(i + 1) as usize] >= b'5' {
i_round = 1;
zbuf[i as usize] = b'0';
i -= 1;
n += 1;
i_dp += 1;
}
}
if i_round > 0 && (i_round < n || n > mx_round) {
if i_round > mx_round {
i_round = mx_round;
}
n = i_round;
let zbase = (i + 1) as usize;
if zbuf[zbase + i_round as usize] >= b'5' {
let mut j = i_round - 1;
loop {
zbuf[zbase + j as usize] += 1;
if zbuf[zbase + j as usize] <= b'9' {
break;
}
zbuf[zbase + j as usize] = b'0';
if j == 0 {
zbuf[i as usize] = b'1';
i -= 1;
n += 1;
i_dp += 1;
break;
} else {
j -= 1;
}
}
}
}
let zbase = (i + 1) as usize;
let mut digits: Vec<u8> = zbuf[zbase..zbase + n as usize].to_vec();
while digits.len() > 1 && *digits.last().unwrap() == b'0' {
digits.pop();
}
FpDecode {
sign,
is_special: 0,
n: digits.len(),
i_dp,
digits,
}
}
}
pub fn format(
r: f64,
mut precision: i32,
mut xtype: XType,
altform2: bool,
altform: bool,
) -> String {
let i_round = match xtype {
XType::Float => -precision,
XType::Generic => {
if precision == 0 {
precision = 1;
}
precision
}
XType::Exp => precision + 1,
};
let s = FpDecode::decode(r, i_round, if altform2 { 26 } else { 16 });
if s.is_special != 0 {
let mut out = String::new();
if s.sign == b'-' {
out.push('-');
}
out.push_str(if s.is_special == 2 { "NaN" } else { "Inf" });
return out;
}
let mut out = String::new();
if s.sign == b'-' {
out.push('-');
}
let exp = s.i_dp - 1;
let flag_rtz;
if xtype == XType::Generic {
precision -= 1;
flag_rtz = !altform;
if exp < -4 || exp > precision {
xtype = XType::Exp;
} else {
precision -= exp;
xtype = XType::Float;
}
} else {
flag_rtz = altform2;
}
let mut e2 = if xtype == XType::Exp { 0 } else { s.i_dp - 1 };
let flag_dp = precision > 0 || altform || altform2;
let mut j = 0usize;
let digit = |j: &mut usize| -> char {
let c = if *j < s.n { s.digits[*j] } else { b'0' };
*j += 1;
c as char
};
if e2 < 0 {
out.push('0');
} else {
while e2 >= 0 {
out.push(digit(&mut j));
e2 -= 1;
}
}
if flag_dp {
out.push('.');
}
e2 += 1;
while e2 < 0 && precision > 0 {
out.push('0');
precision -= 1;
e2 += 1;
}
while precision > 0 {
out.push(digit(&mut j));
precision -= 1;
}
if flag_rtz && flag_dp {
while out.ends_with('0') {
out.pop();
}
if out.ends_with('.') {
if altform2 {
out.push('0');
} else {
out.pop();
}
}
}
if xtype == XType::Exp {
let mut e = s.i_dp - 1;
out.push('e');
if e < 0 {
out.push('-');
e = -e;
} else {
out.push('+');
}
if e >= 100 {
out.push((b'0' + (e / 100) as u8) as char);
e %= 100;
}
out.push((b'0' + (e / 10) as u8) as char);
out.push((b'0' + (e % 10) as u8) as char);
}
out
}
pub fn quote_real(r: f64) -> String {
if r.is_nan() {
return String::from("NaN");
}
if r.is_infinite() {
return format(r, 15, XType::Generic, true, false);
}
let g = format(r, 15, XType::Generic, true, false);
if g.parse::<f64>() == Ok(r) {
g
} else {
format(r, 20, XType::Exp, true, false)
}
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
mod tests {
use super::*;
#[test]
fn quote_real_matches_sqlite_reference() {
let cases: &[(f64, &str)] = &[
(0.0, "0.0"),
(1.5, "1.5"),
(0.1, "0.1"),
(100.0, "100.0"),
(-2.5, "-2.5"),
(2.0 / 3.0, "6.666666666666666296e-01"),
(1.0 / 3.0, "3.333333333333333148e-01"),
(3.141592653589793, "3.141592653589793116e+00"),
(123456789012345.6, "1.234567890123455938e+14"),
(9223372036854775808.0, "9.22337203685477581e+18"),
(1e20, "1.0e+20"),
(1e-20, "1.0e-20"),
];
for &(r, want) in cases {
assert_eq!(quote_real(r), want, "quote_real({r})");
}
}
#[test]
fn quote_real_round_trips() {
let vals = [
0.1,
2.0 / 3.0,
3.141592653589793,
2.718281828459045,
1.0 / 7.0,
123.456,
9.87654321e-5,
-4.2e30,
1.7976931348623157e308,
5e-324, ];
for r in vals {
let q = quote_real(r);
assert_eq!(q.parse::<f64>(), Ok(r), "round-trip {r} via {q}");
}
}
}