use super::FloatFormat;
use super::super::OutputBuffer;
use super::ValidateResult;
const F32_MANT: usize = 2;
const F32_EXP: usize = 2; const F32_WIDTH: usize = 3 + F32_MANT + 2 + F32_EXP; const F64_MANT: usize = 15;
const F64_EXP: usize = 3; const F64_WIDTH: usize = 3 + F64_MANT + 2 + F64_EXP;
const E4M3_INT: usize = 3;
const E4M3_DEC: usize = 3;
const E4M3_WIDTH: usize = 1 + E4M3_INT + 1 + E4M3_DEC;
const E5M2_INT: usize = 5;
const E5M2_DEC: usize = 6;
const E5M2_WIDTH: usize = 1 + E5M2_INT + 1 + E5M2_DEC;
#[inline(always)]
pub(super) fn bytes_count(format: FloatFormat) -> u8 {
match format {
FloatFormat::Scientific32 => 4,
FloatFormat::Scientific64 => 8,
FloatFormat::E4M3 | FloatFormat::E5M2 => 1,
}
}
fn value_from_bytes(bytes: [u8; 8], format: FloatFormat) -> f64 {
match format {
FloatFormat::Scientific32 => {
f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64
}
FloatFormat::Scientific64 => f64::from_le_bytes(bytes),
FloatFormat::E4M3 => e4m3_to_f64(bytes[0]),
FloatFormat::E5M2 => e5m2_to_f64(bytes[0]),
}
}
fn e4m3_to_f64(byte: u8) -> f64 {
let sign = byte >> 7;
let exp = (byte >> 3) & 0xF;
let frac = byte & 0x7;
let v = match exp {
0 if frac == 0 => 0.0,
0 => 2f64.powi(-6) * (frac as f64 / 8.0),
15 => f64::NAN,
_ => 2f64.powi(exp as i32 - 7) * (1.0 + frac as f64 / 8.0),
};
if sign != 0 { -v } else { v }
}
fn e5m2_to_f64(byte: u8) -> f64 {
let sign = byte >> 7;
let exp = (byte >> 2) & 0x1F;
let frac = byte & 0x3;
let v = match exp {
0 if frac == 0 => 0.0,
0 => 2f64.powi(-14) * (frac as f64 / 4.0),
31 if frac == 0 => f64::INFINITY,
31 => f64::NAN,
_ => 2f64.powi(exp as i32 - 15) * (1.0 + frac as f64 / 4.0),
};
if sign != 0 { -v } else { v }
}
fn f64_to_e4m3(v: f64) -> u8 {
if v.is_nan() || !v.is_finite() {
return 0x7F;
}
if v == 0.0 {
return if v.is_sign_negative() { 0x80 } else { 0x00 };
}
nearest_fp8_byte(v, e4m3_to_f64, |b| e4m3_to_f64(b).is_finite())
}
fn f64_to_e5m2(v: f64) -> u8 {
if v.is_nan() {
return 0x7F;
}
if v.is_infinite() {
return if v.is_sign_negative() { 0xFC } else { 0x7C };
}
if v == 0.0 {
return if v.is_sign_negative() { 0x80 } else { 0x00 };
}
nearest_fp8_byte(v, e5m2_to_f64, |b| e5m2_to_f64(b).is_finite())
}
fn nearest_fp8_byte(v: f64, decode: fn(u8) -> f64, is_finite: fn(u8) -> bool) -> u8 {
let mut best = 0u8;
let mut best_err = f64::INFINITY;
for b in 0..=255u8 {
if !is_finite(b) {
continue;
}
let err = (decode(b) - v).abs();
if err < best_err {
best_err = err;
best = b;
}
}
best
}
pub(super) fn display_chars(format: FloatFormat) -> u32 {
match format {
FloatFormat::Scientific32 => F32_WIDTH as u32, FloatFormat::Scientific64 => F64_WIDTH as u32, FloatFormat::E4M3 => E4M3_WIDTH as u32, FloatFormat::E5M2 => E5M2_WIDTH as u32, }
}
pub(super) fn write(bytes: [u8; 8], format: FloatFormat, output: &mut OutputBuffer) {
match format {
FloatFormat::Scientific32 | FloatFormat::Scientific64 => {
write_scientific(bytes, format, output);
}
FloatFormat::E4M3 => {
write_fixed_decimal(value_from_bytes(bytes, format), E4M3_INT, E4M3_DEC, E4M3_WIDTH, output);
}
FloatFormat::E5M2 => {
write_fixed_decimal(value_from_bytes(bytes, format), E5M2_INT, E5M2_DEC, E5M2_WIDTH, output);
}
}
}
fn write_scientific(bytes: [u8; 8], format: FloatFormat, output: &mut OutputBuffer) {
let (mant, exp_digits, width) = match format {
FloatFormat::Scientific32 => (F32_MANT, F32_EXP, F32_WIDTH),
FloatFormat::Scientific64 => (F64_MANT, F64_EXP, F64_WIDTH),
_ => unreachable!(),
};
let v = value_from_bytes(bytes, format);
if !v.is_finite() {
let s: &str = if v.is_nan() { "NaN" } else if v > 0.0 { "inf" } else { "-inf" };
emit_padded(s.as_bytes(), width, output);
return;
}
let neg = v.is_sign_negative();
let a = v.abs();
let (mantissa, exp): (f64, i32) = if a == 0.0 {
(0.0, 0)
} else {
let e = a.log10().floor() as i32;
(a / 10f64.powi(e), e)
};
let scale = 10f64.powi(mant as i32);
let mut m = (mantissa * scale).round() / scale;
let mut e = exp;
if m >= 10.0 {
m /= 10.0;
e += 1;
}
let mut buf = [0u8; 32]; let mut n = 0usize;
buf[n] = if neg { b'-' } else { b'+' };
n += 1;
let lead = m.trunc() as u8; buf[n] = b'0' + lead;
n += 1;
buf[n] = b'.';
n += 1;
let mut frac = m - (lead as f64);
for _ in 0..mant {
frac *= 10.0;
let d = frac.trunc() as u8; buf[n] = b'0' + d;
n += 1;
frac -= d as f64;
}
buf[n] = b'e';
n += 1;
buf[n] = if e < 0 { b'-' } else { b'+' };
n += 1;
let mut ae = e.unsigned_abs();
let start = n;
for i in 0..exp_digits {
let place = 10u32.pow((exp_digits - 1 - i) as u32);
let d = ((ae / place) % 10) as u8;
buf[start + i] = b'0' + d;
n += 1;
}
let _ = &mut ae;
debug_assert_eq!(n, width, "float field width mismatch");
emit_padded(&buf[..n], width, output);
}
fn write_fixed_decimal(v: f64, int_digits: usize, dec_digits: usize, width: usize, output: &mut OutputBuffer) {
if !v.is_finite() {
let s: &str = if v.is_nan() { "NaN" } else if v > 0.0 { "inf" } else { "-inf" };
emit_padded(s.as_bytes(), width, output);
return;
}
let neg = v.is_sign_negative();
let a = v.abs();
let scale = 10f64.powi(dec_digits as i32);
let rounded = (a * scale).round() / scale;
let lead = rounded.trunc() as u64;
let mut frac = rounded - lead as f64;
let mut buf = [0u8; 32];
let mut n = 0usize;
buf[n] = if neg { b'-' } else { b'+' };
n += 1;
let int_start = n;
for i in 0..int_digits {
let place = 10u64.pow((int_digits - 1 - i) as u32);
let d = ((lead / place) % 10) as u8;
buf[int_start + i] = b'0' + d;
n += 1;
}
buf[n] = b'.';
n += 1;
for _ in 0..dec_digits {
frac *= 10.0;
let d = frac.trunc() as u8;
buf[n] = b'0' + d;
n += 1;
frac -= d as f64;
}
debug_assert_eq!(n, width, "fp8 field width mismatch");
emit_padded(&buf[..n], width, output);
}
fn emit_padded(src: &[u8], width: usize, output: &mut OutputBuffer) {
let src = &src[..src.len().min(width)];
let pad = width - src.len();
let mut i = 0;
while i < pad {
output.set(i, b' ');
i += 1;
}
for (j, &b) in src.iter().enumerate() {
output.set(pad + j, b);
}
output.set_len(width as u8);
}
pub(super) fn validate(text: &str, format: FloatFormat) -> ValidateResult {
let ok = match format {
FloatFormat::Scientific32 | FloatFormat::Scientific64 => is_float_prefix(text),
FloatFormat::E4M3 | FloatFormat::E5M2 => is_fixed_decimal_prefix(text),
};
if ok {
ValidateResult::Valid
} else {
ValidateResult::FormatError
}
}
pub(super) fn convert_to_bytes(text: &str, format: FloatFormat) -> ([u8; 8], u8) {
let t = text.trim();
let mut out = [0u8; 8];
match format {
FloatFormat::Scientific32 => match t.parse::<f32>() {
Ok(v) => {
out[..4].copy_from_slice(&v.to_le_bytes());
(out, 4)
}
Err(_) => (out, 0),
},
FloatFormat::Scientific64 => match t.parse::<f64>() {
Ok(v) => {
out.copy_from_slice(&v.to_le_bytes());
(out, 8)
}
Err(_) => (out, 0),
},
FloatFormat::E4M3 => match t.parse::<f64>() {
Ok(v) => {
out[0] = f64_to_e4m3(v);
(out, 1)
}
Err(_) => (out, 0),
},
FloatFormat::E5M2 => match t.parse::<f64>() {
Ok(v) => {
out[0] = f64_to_e5m2(v);
(out, 1)
}
Err(_) => (out, 0),
},
}
}
fn is_float_prefix(text: &str) -> bool {
let t = text.trim();
if t.is_empty() {
return true;
}
let lower = t.trim_start_matches(['+', '-']).to_ascii_lowercase();
if "inf".starts_with(&lower) || "nan".starts_with(&lower) || "infinity".starts_with(&lower) {
return true;
}
let bytes = t.as_bytes();
let mut i = 0;
if bytes[i] == b'+' || bytes[i] == b'-' {
i += 1;
}
let mut seen_dot = false;
let mut seen_e = false;
let mut seen_digit = false;
while i < bytes.len() {
match bytes[i] {
b'0'..=b'9' => seen_digit = true,
b'.' if !seen_dot && !seen_e => seen_dot = true,
b'e' | b'E' if !seen_e && seen_digit => {
seen_e = true;
if i + 1 < bytes.len() && (bytes[i + 1] == b'+' || bytes[i + 1] == b'-') {
i += 1;
}
}
_ => return false,
}
i += 1;
}
true
}
fn is_fixed_decimal_prefix(text: &str) -> bool {
let t = text.trim();
if t.is_empty() {
return true;
}
let lower = t.trim_start_matches(['+', '-']).to_ascii_lowercase();
if "inf".starts_with(&lower) || "nan".starts_with(&lower) || "infinity".starts_with(&lower) {
return true;
}
let bytes = t.as_bytes();
let mut i = 0;
if bytes[i] == b'+' || bytes[i] == b'-' {
i += 1;
}
let mut seen_dot = false;
while i < bytes.len() {
match bytes[i] {
b'0'..=b'9' => {}
b'.' if !seen_dot => seen_dot = true,
_ => return false,
}
i += 1;
}
true
}