#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Style {
Typeless,
Data,
Float,
Integer,
}
pub(crate) fn value(bits: u32, style: Style) -> String {
match style {
Style::Float => float(bits),
Style::Integer => integer(bits),
Style::Typeless | Style::Data => {
let exponent = (bits >> 23) & 0xff;
if (exponent == 0 || exponent == 0xff) && bits != 0x8000_0000 {
integer(bits)
} else {
float(bits)
}
}
}
}
pub(crate) fn integer(bits: u32) -> String {
let signed = bits.cast_signed();
if (-10000..=10000).contains(&signed) {
signed.to_string()
} else {
format!("0x{bits:08x}")
}
}
pub(crate) fn float(bits: u32) -> String {
let value = f32::from_bits(bits);
let class = if value.is_infinite() {
Class::Infinite
} else if bits == 0xffc0_0000 {
Class::Indefinite
} else if value.is_nan() {
Class::Quiet
} else {
Class::Finite
};
special(class, bits >> 31 != 0).unwrap_or_else(|| format!("{:.6}", f64::from(value)))
}
pub(crate) fn double(low: u32, high: u32) -> String {
let bits = (u64::from(high) << 32) | u64::from(low);
let value = f64::from_bits(bits);
let class = if value.is_infinite() {
Class::Infinite
} else if bits == 0xfff8_0000_0000_0000 {
Class::Indefinite
} else if value.is_nan() && bits & (1 << 51) == 0 {
Class::Signaling
} else if value.is_nan() {
Class::Quiet
} else {
Class::Finite
};
let text = special(class, high >> 31 != 0).unwrap_or_else(|| format!("{value:.6}"));
format!("{text}l")
}
enum Class {
Finite,
Infinite,
Indefinite,
Quiet,
Signaling,
}
fn special(class: Class, negative: bool) -> Option<String> {
let sign = if negative { "-" } else { "" };
match class {
Class::Finite => None,
Class::Infinite => Some(format!("{sign}1.#INF00")),
Class::Indefinite => Some("-1.#IND00".to_owned()),
Class::Quiet => Some(format!("{sign}1.#QNAN0")),
Class::Signaling => Some(format!("{sign}1.#SNAN0")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn finite(text: &str, value: f64, negative: bool) -> std::result::Result<(), TestCaseError> {
let (whole, fraction) = text.trim_start_matches('-').split_once('.').unwrap_or_default();
prop_assert!(
!whole.is_empty() && whole.bytes().all(|b| b.is_ascii_digit()),
"{}",
text
);
prop_assert!(
fraction.len() == 6 && fraction.bytes().all(|b| b.is_ascii_digit()),
"{}",
text
);
prop_assert_eq!(text.starts_with('-'), negative, "{}", text);
let read: f64 = text.parse().unwrap();
let ulp = f64::from_bits(value.abs().to_bits().saturating_add(1)) - value.abs();
prop_assert!((read - value).abs() <= 0.5e-6 + ulp, "{} for {:e}", text, value);
Ok(())
}
fn special(negative: bool, mantissa: u64, quiet_bit: u64, widened: bool) -> String {
let sign = if negative { "-" } else { "" };
if mantissa == 0 {
format!("{sign}1.#INF00")
} else if negative && mantissa == quiet_bit {
"-1.#IND00".to_owned()
} else if mantissa & quiet_bit != 0 || widened {
format!("{sign}1.#QNAN0")
} else {
format!("{sign}1.#SNAN0")
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 4096, failure_persistence: None, ..ProptestConfig::default() })]
#[test]
fn a_float_is_spelled_as_percent_f_spells_it(
bits in prop_oneof![2 => any::<u32>(), 1 => any::<u32>().prop_map(|b| b | 0x7f80_0000)],
) {
let text = float(bits);
let negative = bits >> 31 != 0;
if (bits >> 23) & 0xff == 0xff {
prop_assert_eq!(text, special(negative, u64::from(bits & 0x7f_ffff), 1 << 22, true));
} else {
finite(&text, f64::from(f32::from_bits(bits)), negative)?;
}
}
#[test]
fn a_double_is_spelled_as_percent_f_spells_it(
low in prop_oneof![1 => Just(0u32), 3 => any::<u32>()],
high in prop_oneof![2 => any::<u32>(), 1 => any::<u32>().prop_map(|h| h | 0x7ff0_0000)],
) {
let text = double(low, high);
let bits = (u64::from(high) << 32) | u64::from(low);
let negative = high >> 31 != 0;
let body = text.strip_suffix('l').unwrap();
if (bits >> 52) & 0x7ff == 0x7ff {
prop_assert_eq!(body, special(negative, bits & ((1 << 52) - 1), 1 << 51, false));
} else {
finite(body, f64::from_bits(bits), negative)?;
}
}
#[test]
fn an_integer_is_decimal_to_ten_thousand_and_hex_beyond(bits in any::<u32>()) {
let text = integer(bits);
let signed = bits.cast_signed();
if (-10000..=10000).contains(&signed) {
prop_assert_eq!(text.parse::<i32>().unwrap(), signed);
} else {
let hex = text.strip_prefix("0x").unwrap();
prop_assert_eq!(hex.len(), 8);
prop_assert_eq!(u32::from_str_radix(hex, 16).unwrap(), bits);
}
}
}
#[test]
fn floats_and_integers() {
assert_eq!(float(0x3f80_0000), "1.000000");
assert_eq!(float(0x7149_f2ca), "1000000015047466219876688855040.000000");
assert_eq!(float(0x8000_0000), "-0.000000");
assert_eq!(float(0x7f80_0000), "1.#INF00");
assert_eq!(integer(0xffff_fc7c), "-900");
assert_eq!(integer(10001), "0x00002711");
assert_eq!(value(0x8000_0000, Style::Typeless), "-0.000000");
assert_eq!(value(1, Style::Typeless), "1");
assert_eq!(value(0x3f00_0000, Style::Typeless), "0.500000");
}
#[test]
fn doubles_keep_a_signaling_nan() {
assert_eq!(double(0, 0xfff0_0000), "-1.#INF00l");
assert_eq!(double(0, 0x7ff0_0000), "1.#INF00l");
assert_eq!(double(1, 0x7ff0_0001), "1.#SNAN0l");
assert_eq!(double(1, 0xfff0_0001), "-1.#SNAN0l");
assert_eq!(double(0, 0xfff8_0000), "-1.#IND00l");
assert_eq!(double(5, 0x7ff8_0000), "1.#QNAN0l");
assert_eq!(double(5, 0xfff8_0000), "-1.#QNAN0l");
}
}