//! Immediate operands for Cretonne instructions //! //! This module defines the types of immediate operands that can appear on Cretonne instructions. //! Each type here should have a corresponding definition in the `cretonne.immediates` Python //! module in the meta language. use std::fmt::{self, Display, Formatter}; use std::{i32, u32}; use std::mem; use std::str::FromStr; /// 64-bit immediate integer operand. /// /// An `Imm64` operand can also be used to represent immediate values of smaller integer types by /// sign-extending to `i64`. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Imm64(i64); impl Imm64 { /// Create a new `Imm64` representing the signed number `x`. pub fn new(x: i64) -> Imm64 { Imm64(x) } } impl Into for Imm64 { fn into(self) -> i64 { self.0 } } impl From for Imm64 { fn from(x: i64) -> Self { Imm64(x) } } // Hexadecimal with a multiple of 4 digits and group separators: // // 0xfff0 // 0x0001_ffff // 0xffff_ffff_fff8_4400 // fn write_hex(x: i64, f: &mut Formatter) -> fmt::Result { let mut pos = (64 - x.leading_zeros() - 1) & 0xf0; write!(f, "0x{:04x}", (x >> pos) & 0xffff)?; while pos > 0 { pos -= 16; write!(f, "_{:04x}", (x >> pos) & 0xffff)?; } Ok(()) } impl Display for Imm64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let x = self.0; if -10_000 < x && x < 10_000 { // Use decimal for small numbers. write!(f, "{}", x) } else { write_hex(x, f) } } } /// Parse a 64-bit number. fn parse_i64(s: &str) -> Result { let mut value: u64 = 0; let mut digits = 0; let negative = s.starts_with('-'); let s2 = if negative || s.starts_with('+') { &s[1..] } else { s }; if s2.starts_with("0x") { // Hexadecimal. for ch in s2[2..].chars() { match ch.to_digit(16) { Some(digit) => { digits += 1; if digits > 16 { return Err("Too many hexadecimal digits"); } // This can't overflow given the digit limit. value = (value << 4) | digit as u64; } None => { // Allow embedded underscores, but fail on anything else. if ch != '_' { return Err("Invalid character in hexadecimal number"); } } } } } else { // Decimal number, possibly negative. for ch in s2.chars() { match ch.to_digit(16) { Some(digit) => { digits += 1; match value.checked_mul(10) { None => return Err("Too large decimal number"), Some(v) => value = v, } match value.checked_add(digit as u64) { None => return Err("Too large decimal number"), Some(v) => value = v, } } None => { // Allow embedded underscores, but fail on anything else. if ch != '_' { return Err("Invalid character in decimal number"); } } } } } if digits == 0 { return Err("No digits in number"); } // We support the range-and-a-half from -2^63 .. 2^64-1. if negative { value = value.wrapping_neg(); // Don't allow large negative values to wrap around and become positive. if value as i64 > 0 { return Err("Negative number too small"); } } Ok(value as i64) } impl FromStr for Imm64 { type Err = &'static str; // Parse a decimal or hexadecimal `Imm64`, formatted as above. fn from_str(s: &str) -> Result { parse_i64(s).map(Imm64::new) } } /// 8-bit unsigned integer immediate operand. /// /// This is used to indicate lane indexes typically. pub type Uimm8 = u8; /// 32-bit signed immediate offset. /// /// This is used to encode an immediate offset for load/store instructions. All supported ISAs have /// a maximum load/store offset that fits in an `i32`. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Offset32(i32); impl Offset32 { /// Create a new `Offset32` representing the signed number `x`. pub fn new(x: i32) -> Offset32 { Offset32(x) } } impl Into for Offset32 { fn into(self) -> i32 { self.0 } } impl Into for Offset32 { fn into(self) -> i64 { self.0 as i64 } } impl From for Offset32 { fn from(x: i32) -> Self { Offset32(x) } } impl Display for Offset32 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // 0 displays as an empty offset. if self.0 == 0 { return Ok(()); } // Always include a sign. write!(f, "{}", if self.0 < 0 { '-' } else { '+' })?; let val = (self.0 as i64).abs(); if val < 10_000 { write!(f, "{}", val) } else { write_hex(val, f) } } } impl FromStr for Offset32 { type Err = &'static str; // Parse a decimal or hexadecimal `Offset32`, formatted as above. fn from_str(s: &str) -> Result { if !(s.starts_with('-') || s.starts_with('+')) { return Err("Offset must begin with sign"); } parse_i64(s).and_then(|x| if i32::MIN as i64 <= x && x <= i32::MAX as i64 { Ok(Offset32::new(x as i32)) } else { Err("Offset out of range") }) } } /// 32-bit unsigned immediate offset. /// /// This is used to encode an immediate offset for WebAssembly heap_load/heap_store instructions. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Uoffset32(u32); impl Uoffset32 { /// Create a new `Uoffset32` representing the number `x`. pub fn new(x: u32) -> Uoffset32 { Uoffset32(x) } } impl Into for Uoffset32 { fn into(self) -> u32 { self.0 } } impl Into for Uoffset32 { fn into(self) -> i64 { self.0 as i64 } } impl From for Uoffset32 { fn from(x: u32) -> Self { Uoffset32(x) } } impl Display for Uoffset32 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // 0 displays as an empty offset. if self.0 == 0 { return Ok(()); } // Always include a sign. if self.0 < 10_000 { write!(f, "+{}", self.0) } else { write!(f, "+")?; write_hex(self.0 as i64, f) } } } impl FromStr for Uoffset32 { type Err = &'static str; // Parse a decimal or hexadecimal `Uoffset32`, formatted as above. fn from_str(s: &str) -> Result { if !s.starts_with('+') { return Err("Unsigned offset must begin with '+' sign"); } parse_i64(s).and_then(|x| if 0 <= x && x <= u32::MAX as i64 { Ok(Uoffset32::new(x as u32)) } else { Err("Offset out of range") }) } } /// An IEEE binary32 immediate floating point value. /// /// All bit patterns are allowed. #[derive(Copy, Clone, Debug)] pub struct Ieee32(f32); /// An IEEE binary64 immediate floating point value. /// /// All bit patterns are allowed. #[derive(Copy, Clone, Debug)] pub struct Ieee64(f64); // Format a floating point number in a way that is reasonably human-readable, and that can be // converted back to binary without any rounding issues. The hexadecimal formatting of normal and // subnormal numbers is compatible with C99 and the `printf "%a"` format specifier. The NaN and Inf // formats are not supported by C99. // // The encoding parameters are: // // w - exponent field width in bits // t - trailing significand field width in bits // fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { debug_assert!(w > 0 && w <= 16, "Invalid exponent range"); debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64"); debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size"); let max_e_bits = (1u64 << w) - 1; let t_bits = bits & ((1u64 << t) - 1); // Trailing significand. let e_bits = (bits >> t) & max_e_bits; // Biased exponent. let sign_bit = (bits >> w + t) & 1; let bias: i32 = (1 << (w - 1)) - 1; let e = e_bits as i32 - bias; // Unbiased exponent. let emin = 1 - bias; // Minimum exponent. // How many hexadecimal digits are needed for the trailing significand? let digits = (t + 3) / 4; // Trailing significand left-aligned in `digits` hexadecimal digits. let left_t_bits = t_bits << (4 * digits - t); // All formats share the leading sign. if sign_bit != 0 { write!(f, "-")?; } if e_bits == 0 { if t_bits == 0 { // Zero. write!(f, "0.0") } else { // Subnormal. write!(f, "0x0.{0:01$x}p{2}", left_t_bits, digits as usize, emin) } } else if e_bits == max_e_bits { // Always print a `+` or `-` sign for these special values. // This makes them easier to parse as they can't be confused as identifiers. if sign_bit == 0 { write!(f, "+")?; } if t_bits == 0 { // Infinity. write!(f, "Inf") } else { // NaN. let payload = t_bits & ((1 << (t - 1)) - 1); if t_bits & (1 << (t - 1)) != 0 { // Quiet NaN. if payload != 0 { write!(f, "NaN:0x{:x}", payload) } else { write!(f, "NaN") } } else { // Signaling NaN. write!(f, "sNaN:0x{:x}", payload) } } } else { // Normal number. write!(f, "0x1.{0:01$x}p{2}", left_t_bits, digits as usize, e) } } // Parse a float using the same format as `format_float` above. // // The encoding parameters are: // // w - exponent field width in bits // t - trailing significand field width in bits // fn parse_float(s: &str, w: u8, t: u8) -> Result { debug_assert!(w > 0 && w <= 16, "Invalid exponent range"); debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64"); debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size"); let (sign_bit, s2) = if s.starts_with('-') { (1u64 << t + w, &s[1..]) } else if s.starts_with('+') { (0, &s[1..]) } else { (0, s) }; if !s2.starts_with("0x") { let max_e_bits = ((1u64 << w) - 1) << t; let quiet_bit = 1u64 << (t - 1); // The only decimal encoding allowed is 0. if s2 == "0.0" { return Ok(sign_bit); } if s2 == "Inf" { // +/- infinity: e = max, t = 0. return Ok(sign_bit | max_e_bits); } if s2 == "NaN" { // Canonical quiet NaN: e = max, t = quiet. return Ok(sign_bit | max_e_bits | quiet_bit); } if s2.starts_with("NaN:0x") { // Quiet NaN with payload. return match u64::from_str_radix(&s2[6..], 16) { Ok(payload) if payload < quiet_bit => { Ok(sign_bit | max_e_bits | quiet_bit | payload) } _ => Err("Invalid NaN payload"), }; } if s2.starts_with("sNaN:0x") { // Signaling NaN with payload. return match u64::from_str_radix(&s2[7..], 16) { Ok(payload) if 0 < payload && payload < quiet_bit => { Ok(sign_bit | max_e_bits | payload) } _ => Err("Invalid sNaN payload"), }; } return Err("Float must be hexadecimal"); } let s3 = &s2[2..]; let mut digits = 0u8; let mut digits_before_period: Option = None; let mut significand = 0u64; let mut exponent = 0i32; for (idx, ch) in s3.char_indices() { match ch { '.' => { // This is the radix point. There can only be one. if digits_before_period != None { return Err("Multiple radix points"); } else { digits_before_period = Some(digits); } } 'p' => { // The following exponent is a decimal number. let exp_str = &s3[1 + idx..]; match exp_str.parse::() { Ok(e) => { exponent = e as i32; break; } Err(_) => return Err("Bad exponent"), } } _ => { match ch.to_digit(16) { Some(digit) => { digits += 1; if digits > 16 { return Err("Too many digits"); } significand = (significand << 4) | digit as u64; } None => return Err("Invalid character"), } } } } if digits == 0 { return Err("No digits"); } if significand == 0 { // This is +/- 0.0. return Ok(sign_bit); } // Number of bits appearing after the radix point. match digits_before_period { None => {} // No radix point present. Some(d) => exponent -= 4 * (digits - d) as i32, }; // Normalize the significand and exponent. let significant_bits = (64 - significand.leading_zeros()) as u8; if significant_bits > t + 1 { let adjust = significant_bits - (t + 1); if significand & ((1u64 << adjust) - 1) != 0 { return Err("Too many significant bits"); } // Adjust significand down. significand >>= adjust; exponent += adjust as i32; } else { let adjust = t + 1 - significant_bits; significand <<= adjust; exponent -= adjust as i32; } assert_eq!(significand >> t, 1); // Trailing significand excludes the high bit. let t_bits = significand & ((1 << t) - 1); let max_exp = (1i32 << w) - 2; let bias: i32 = (1 << (w - 1)) - 1; exponent += bias + t as i32; if exponent > max_exp { Err("Magnitude too large") } else if exponent > 0 { // This is a normal number. let e_bits = (exponent as u64) << t; Ok(sign_bit | e_bits | t_bits) } else if 1 - exponent <= t as i32 { // This is a subnormal number: e = 0, t = significand bits. // Renormalize significand for exponent = 1. let adjust = 1 - exponent; if significand & ((1u64 << adjust) - 1) != 0 { Err("Subnormal underflow") } else { significand >>= adjust; Ok(sign_bit | significand) } } else { Err("Magnitude too small") } } impl Ieee32 { /// Create a new `Ieee32` representing the number `x`. pub fn new(x: f32) -> Ieee32 { Ieee32(x) } /// Construct `Ieee32` immediate from raw bits. pub fn from_bits(x: u32) -> Ieee32 { Ieee32(unsafe { mem::transmute(x) }) } } impl Display for Ieee32 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let bits: u32 = unsafe { mem::transmute(self.0) }; format_float(bits as u64, 8, 23, f) } } impl FromStr for Ieee32 { type Err = &'static str; fn from_str(s: &str) -> Result { match parse_float(s, 8, 23) { Ok(b) => Ok(Ieee32::from_bits(b as u32)), Err(s) => Err(s), } } } impl Ieee64 { /// Create a new `Ieee64` representing the number `x`. pub fn new(x: f64) -> Ieee64 { Ieee64(x) } /// Construct `Ieee64` immediate from raw bits. pub fn from_bits(x: u64) -> Ieee64 { Ieee64(unsafe { mem::transmute(x) }) } } impl Display for Ieee64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let bits: u64 = unsafe { mem::transmute(self.0) }; format_float(bits, 11, 52, f) } } impl FromStr for Ieee64 { type Err = &'static str; fn from_str(s: &str) -> Result { match parse_float(s, 11, 52) { Ok(b) => Ok(Ieee64::from_bits(b)), Err(s) => Err(s), } } } #[cfg(test)] mod tests { use super::*; use std::{f32, f64}; use std::str::FromStr; use std::fmt::Display; #[test] fn format_imm64() { assert_eq!(Imm64(0).to_string(), "0"); assert_eq!(Imm64(9999).to_string(), "9999"); assert_eq!(Imm64(10000).to_string(), "0x2710"); assert_eq!(Imm64(-9999).to_string(), "-9999"); assert_eq!(Imm64(-10000).to_string(), "0xffff_ffff_ffff_d8f0"); assert_eq!(Imm64(0xffff).to_string(), "0xffff"); assert_eq!(Imm64(0x10000).to_string(), "0x0001_0000"); } // Verify that `text` can be parsed as a `T` into a value that displays as `want`. fn parse_ok(text: &str, want: &str) where ::Err: Display { match text.parse::() { Err(s) => panic!("\"{}\".parse() error: {}", text, s), Ok(x) => assert_eq!(x.to_string(), want), } } // Verify that `text` fails to parse as `T` with the error `msg`. fn parse_err(text: &str, msg: &str) where ::Err: Display { match text.parse::() { Err(s) => assert_eq!(s.to_string(), msg), Ok(x) => panic!("Wanted Err({}), but got {}", msg, x), } } #[test] fn parse_imm64() { parse_ok::("0", "0"); parse_ok::("1", "1"); parse_ok::("-0", "0"); parse_ok::("-1", "-1"); parse_ok::("0x0", "0"); parse_ok::("0xf", "15"); parse_ok::("-0x9", "-9"); // Probe limits. parse_ok::("0xffffffff_ffffffff", "-1"); parse_ok::("0x80000000_00000000", "0x8000_0000_0000_0000"); parse_ok::("-0x80000000_00000000", "0x8000_0000_0000_0000"); parse_err::("-0x80000000_00000001", "Negative number too small"); parse_ok::("18446744073709551615", "-1"); parse_ok::("-9223372036854775808", "0x8000_0000_0000_0000"); // Overflow both the `checked_add` and `checked_mul`. parse_err::("18446744073709551616", "Too large decimal number"); parse_err::("184467440737095516100", "Too large decimal number"); parse_err::("-9223372036854775809", "Negative number too small"); // Underscores are allowed where digits go. parse_ok::("0_0", "0"); parse_ok::("-_10_0", "-100"); parse_ok::("_10_", "10"); parse_ok::("0x97_88_bb", "0x0097_88bb"); parse_ok::("0x_97_", "151"); parse_err::("", "No digits in number"); parse_err::("-", "No digits in number"); parse_err::("_", "No digits in number"); parse_err::("0x", "No digits in number"); parse_err::("0x_", "No digits in number"); parse_err::("-0x", "No digits in number"); parse_err::(" ", "Invalid character in decimal number"); parse_err::("0 ", "Invalid character in decimal number"); parse_err::(" 0", "Invalid character in decimal number"); parse_err::("--", "Invalid character in decimal number"); parse_err::("-0x-", "Invalid character in hexadecimal number"); // Hex count overflow. parse_err::("0x0_0000_0000_0000_0000", "Too many hexadecimal digits"); } #[test] fn format_offset32() { assert_eq!(Offset32(0).to_string(), ""); assert_eq!(Offset32(1).to_string(), "+1"); assert_eq!(Offset32(-1).to_string(), "-1"); assert_eq!(Offset32(9999).to_string(), "+9999"); assert_eq!(Offset32(10000).to_string(), "+0x2710"); assert_eq!(Offset32(-9999).to_string(), "-9999"); assert_eq!(Offset32(-10000).to_string(), "-0x2710"); assert_eq!(Offset32(0xffff).to_string(), "+0xffff"); assert_eq!(Offset32(0x10000).to_string(), "+0x0001_0000"); } #[test] fn parse_offset32() { parse_ok::("+0", ""); parse_ok::("+1", "+1"); parse_ok::("-0", ""); parse_ok::("-1", "-1"); parse_ok::("+0x0", ""); parse_ok::("+0xf", "+15"); parse_ok::("-0x9", "-9"); parse_ok::("-0x8000_0000", "-0x8000_0000"); parse_err::("+0x8000_0000", "Offset out of range"); } #[test] fn format_uoffset32() { assert_eq!(Uoffset32(0).to_string(), ""); assert_eq!(Uoffset32(1).to_string(), "+1"); assert_eq!(Uoffset32(9999).to_string(), "+9999"); assert_eq!(Uoffset32(10000).to_string(), "+0x2710"); assert_eq!(Uoffset32(0xffff).to_string(), "+0xffff"); assert_eq!(Uoffset32(0x10000).to_string(), "+0x0001_0000"); } #[test] fn parse_uoffset32() { parse_ok::("+0", ""); parse_ok::("+1", "+1"); parse_ok::("+0x0", ""); parse_ok::("+0xf", "+15"); parse_ok::("+0x8000_0000", "+0x8000_0000"); parse_ok::("+0xffff_ffff", "+0xffff_ffff"); parse_err::("+0x1_0000_0000", "Offset out of range"); } #[test] fn format_ieee32() { assert_eq!(Ieee32::new(0.0).to_string(), "0.0"); assert_eq!(Ieee32::new(-0.0).to_string(), "-0.0"); assert_eq!(Ieee32::new(1.0).to_string(), "0x1.000000p0"); assert_eq!(Ieee32::new(1.5).to_string(), "0x1.800000p0"); assert_eq!(Ieee32::new(0.5).to_string(), "0x1.000000p-1"); assert_eq!(Ieee32::new(f32::EPSILON).to_string(), "0x1.000000p-23"); assert_eq!(Ieee32::new(f32::MIN).to_string(), "-0x1.fffffep127"); assert_eq!(Ieee32::new(f32::MAX).to_string(), "0x1.fffffep127"); // Smallest positive normal number. assert_eq!(Ieee32::new(f32::MIN_POSITIVE).to_string(), "0x1.000000p-126"); // Subnormals. assert_eq!(Ieee32::new(f32::MIN_POSITIVE / 2.0).to_string(), "0x0.800000p-126"); assert_eq!(Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON).to_string(), "0x0.000002p-126"); assert_eq!(Ieee32::new(f32::INFINITY).to_string(), "+Inf"); assert_eq!(Ieee32::new(f32::NEG_INFINITY).to_string(), "-Inf"); assert_eq!(Ieee32::new(f32::NAN).to_string(), "+NaN"); assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "+NaN:0x1"); assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "+NaN:0x300001"); // Signaling NaNs. assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "+sNaN:0x1"); assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "+sNaN:0x200001"); } #[test] fn parse_ieee32() { parse_ok::("0.0", "0.0"); parse_ok::("+0.0", "0.0"); parse_ok::("-0.0", "-0.0"); parse_ok::("0x0", "0.0"); parse_ok::("0x0.0", "0.0"); parse_ok::("0x.0", "0.0"); parse_ok::("0x0.", "0.0"); parse_ok::("0x1", "0x1.000000p0"); parse_ok::("+0x1", "0x1.000000p0"); parse_ok::("-0x1", "-0x1.000000p0"); parse_ok::("0x10", "0x1.000000p4"); parse_ok::("0x10.0", "0x1.000000p4"); parse_err::("0.", "Float must be hexadecimal"); parse_err::(".0", "Float must be hexadecimal"); parse_err::("0", "Float must be hexadecimal"); parse_err::("-0", "Float must be hexadecimal"); parse_err::(".", "Float must be hexadecimal"); parse_err::("", "Float must be hexadecimal"); parse_err::("-", "Float must be hexadecimal"); parse_err::("0x", "No digits"); parse_err::("0x..", "Multiple radix points"); // Check significant bits. parse_ok::("0x0.ffffff", "0x1.fffffep-1"); parse_ok::("0x1.fffffe", "0x1.fffffep0"); parse_ok::("0x3.fffffc", "0x1.fffffep1"); parse_ok::("0x7.fffff8", "0x1.fffffep2"); parse_ok::("0xf.fffff0", "0x1.fffffep3"); parse_err::("0x1.ffffff", "Too many significant bits"); parse_err::("0x1.fffffe0000000000", "Too many digits"); // Exponents. parse_ok::("0x1p3", "0x1.000000p3"); parse_ok::("0x1p-3", "0x1.000000p-3"); parse_ok::("0x1.0p3", "0x1.000000p3"); parse_ok::("0x2.0p3", "0x1.000000p4"); parse_ok::("0x1.0p127", "0x1.000000p127"); parse_ok::("0x1.0p-126", "0x1.000000p-126"); parse_ok::("0x0.1p-122", "0x1.000000p-126"); parse_err::("0x2.0p127", "Magnitude too large"); // Subnormals. parse_ok::("0x1.0p-127", "0x0.800000p-126"); parse_ok::("0x1.0p-149", "0x0.000002p-126"); parse_ok::("0x0.000002p-126", "0x0.000002p-126"); parse_err::("0x0.100001p-126", "Subnormal underflow"); parse_err::("0x1.8p-149", "Subnormal underflow"); parse_err::("0x1.0p-150", "Magnitude too small"); // NaNs and Infs. parse_ok::("Inf", "+Inf"); parse_ok::("+Inf", "+Inf"); parse_ok::("-Inf", "-Inf"); parse_ok::("NaN", "+NaN"); parse_ok::("+NaN", "+NaN"); parse_ok::("-NaN", "-NaN"); parse_ok::("NaN:0x0", "+NaN"); parse_err::("NaN:", "Float must be hexadecimal"); parse_err::("NaN:0", "Float must be hexadecimal"); parse_err::("NaN:0x", "Invalid NaN payload"); parse_ok::("NaN:0x000001", "+NaN:0x1"); parse_ok::("NaN:0x300001", "+NaN:0x300001"); parse_err::("NaN:0x400001", "Invalid NaN payload"); parse_ok::("sNaN:0x1", "+sNaN:0x1"); parse_err::("sNaN:0x0", "Invalid sNaN payload"); parse_ok::("sNaN:0x200001", "+sNaN:0x200001"); parse_err::("sNaN:0x400001", "Invalid sNaN payload"); } #[test] fn format_ieee64() { assert_eq!(Ieee64::new(0.0).to_string(), "0.0"); assert_eq!(Ieee64::new(-0.0).to_string(), "-0.0"); assert_eq!(Ieee64::new(1.0).to_string(), "0x1.0000000000000p0"); assert_eq!(Ieee64::new(1.5).to_string(), "0x1.8000000000000p0"); assert_eq!(Ieee64::new(0.5).to_string(), "0x1.0000000000000p-1"); assert_eq!(Ieee64::new(f64::EPSILON).to_string(), "0x1.0000000000000p-52"); assert_eq!(Ieee64::new(f64::MIN).to_string(), "-0x1.fffffffffffffp1023"); assert_eq!(Ieee64::new(f64::MAX).to_string(), "0x1.fffffffffffffp1023"); // Smallest positive normal number. assert_eq!(Ieee64::new(f64::MIN_POSITIVE).to_string(), "0x1.0000000000000p-1022"); // Subnormals. assert_eq!(Ieee64::new(f64::MIN_POSITIVE / 2.0).to_string(), "0x0.8000000000000p-1022"); assert_eq!(Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON).to_string(), "0x0.0000000000001p-1022"); assert_eq!(Ieee64::new(f64::INFINITY).to_string(), "+Inf"); assert_eq!(Ieee64::new(f64::NEG_INFINITY).to_string(), "-Inf"); assert_eq!(Ieee64::new(f64::NAN).to_string(), "+NaN"); assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), "+NaN:0x1"); assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(), "+NaN:0x4000000000001"); // Signaling NaNs. assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(), "+sNaN:0x1"); assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(), "+sNaN:0x4000000000001"); } #[test] fn parse_ieee64() { parse_ok::("0.0", "0.0"); parse_ok::("-0.0", "-0.0"); parse_ok::("0x0", "0.0"); parse_ok::("0x0.0", "0.0"); parse_ok::("0x.0", "0.0"); parse_ok::("0x0.", "0.0"); parse_ok::("0x1", "0x1.0000000000000p0"); parse_ok::("-0x1", "-0x1.0000000000000p0"); parse_ok::("0x10", "0x1.0000000000000p4"); parse_ok::("0x10.0", "0x1.0000000000000p4"); parse_err::("0.", "Float must be hexadecimal"); parse_err::(".0", "Float must be hexadecimal"); parse_err::("0", "Float must be hexadecimal"); parse_err::("-0", "Float must be hexadecimal"); parse_err::(".", "Float must be hexadecimal"); parse_err::("", "Float must be hexadecimal"); parse_err::("-", "Float must be hexadecimal"); parse_err::("0x", "No digits"); parse_err::("0x..", "Multiple radix points"); // Check significant bits. parse_ok::("0x0.fffffffffffff8", "0x1.fffffffffffffp-1"); parse_ok::("0x1.fffffffffffff", "0x1.fffffffffffffp0"); parse_ok::("0x3.ffffffffffffe", "0x1.fffffffffffffp1"); parse_ok::("0x7.ffffffffffffc", "0x1.fffffffffffffp2"); parse_ok::("0xf.ffffffffffff8", "0x1.fffffffffffffp3"); parse_err::("0x3.fffffffffffff", "Too many significant bits"); parse_err::("0x001.fffffe00000000", "Too many digits"); // Exponents. parse_ok::("0x1p3", "0x1.0000000000000p3"); parse_ok::("0x1p-3", "0x1.0000000000000p-3"); parse_ok::("0x1.0p3", "0x1.0000000000000p3"); parse_ok::("0x2.0p3", "0x1.0000000000000p4"); parse_ok::("0x1.0p1023", "0x1.0000000000000p1023"); parse_ok::("0x1.0p-1022", "0x1.0000000000000p-1022"); parse_ok::("0x0.1p-1018", "0x1.0000000000000p-1022"); parse_err::("0x2.0p1023", "Magnitude too large"); // Subnormals. parse_ok::("0x1.0p-1023", "0x0.8000000000000p-1022"); parse_ok::("0x1.0p-1074", "0x0.0000000000001p-1022"); parse_ok::("0x0.0000000000001p-1022", "0x0.0000000000001p-1022"); parse_err::("0x0.10000000000008p-1022", "Subnormal underflow"); parse_err::("0x1.8p-1074", "Subnormal underflow"); parse_err::("0x1.0p-1075", "Magnitude too small"); // NaNs and Infs. parse_ok::("Inf", "+Inf"); parse_ok::("-Inf", "-Inf"); parse_ok::("NaN", "+NaN"); parse_ok::("-NaN", "-NaN"); parse_ok::("NaN:0x0", "+NaN"); parse_err::("NaN:", "Float must be hexadecimal"); parse_err::("NaN:0", "Float must be hexadecimal"); parse_err::("NaN:0x", "Invalid NaN payload"); parse_ok::("NaN:0x000001", "+NaN:0x1"); parse_ok::("NaN:0x4000000000001", "+NaN:0x4000000000001"); parse_err::("NaN:0x8000000000001", "Invalid NaN payload"); parse_ok::("sNaN:0x1", "+sNaN:0x1"); parse_err::("sNaN:0x0", "Invalid sNaN payload"); parse_ok::("sNaN:0x4000000000001", "+sNaN:0x4000000000001"); parse_err::("sNaN:0x8000000000001", "Invalid sNaN payload"); } }