blob: 9900cb8064e7660df36f3080de37dfd4f918a6c7 [file] [log] [blame]
//! Immediate operands for Cranelift instructions
//!
//! This module defines the types of immediate operands that can appear on Cranelift instructions.
//! Each type here should have a corresponding definition in the
//! `cranelift-codegen/meta/src/shared/immediates` crate in the meta language.
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::convert::TryFrom;
use core::fmt::{self, Display, Formatter};
use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Sub};
use core::str::FromStr;
use core::{i32, u32};
#[cfg(feature = "enable-serde")]
use serde_derive::{Deserialize, Serialize};
/// Convert a type into a vector of bytes; all implementors in this file must use little-endian
/// orderings of bytes to match WebAssembly's little-endianness.
pub trait IntoBytes {
/// Return the little-endian byte representation of the implementing type.
fn into_bytes(self) -> Vec<u8>;
}
impl IntoBytes for u8 {
fn into_bytes(self) -> Vec<u8> {
vec![self]
}
}
impl IntoBytes for i8 {
fn into_bytes(self) -> Vec<u8> {
vec![self as u8]
}
}
impl IntoBytes for i16 {
fn into_bytes(self) -> Vec<u8> {
self.to_le_bytes().to_vec()
}
}
impl IntoBytes for i32 {
fn into_bytes(self) -> Vec<u8> {
self.to_le_bytes().to_vec()
}
}
impl IntoBytes for Vec<u8> {
fn into_bytes(self) -> Vec<u8> {
self
}
}
/// 64-bit immediate signed 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, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Imm64(i64);
impl Imm64 {
/// Create a new `Imm64` representing the signed number `x`.
pub fn new(x: i64) -> Self {
Self(x)
}
/// Return self negated.
pub fn wrapping_neg(self) -> Self {
Self(self.0.wrapping_neg())
}
/// Returns the value of this immediate.
pub fn bits(&self) -> i64 {
self.0
}
/// Sign extend this immediate as if it were a signed integer of the given
/// power-of-two width.
pub fn sign_extend_from_width(&mut self, bit_width: u32) {
debug_assert!(bit_width.is_power_of_two());
if bit_width >= 64 {
return;
}
let bit_width = i64::from(bit_width);
let delta = 64 - bit_width;
let sign_extended = (self.0 << delta) >> delta;
*self = Imm64(sign_extended);
}
}
impl From<Imm64> for i64 {
fn from(val: Imm64) -> i64 {
val.0
}
}
impl IntoBytes for Imm64 {
fn into_bytes(self) -> Vec<u8> {
self.0.to_le_bytes().to_vec()
}
}
impl From<i64> for Imm64 {
fn from(x: i64) -> Self {
Self(x)
}
}
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 as u64, f)
}
}
}
/// Parse a 64-bit signed number.
fn parse_i64(s: &str) -> Result<i64, &'static str> {
let negative = s.starts_with('-');
let s2 = if negative || s.starts_with('+') {
&s[1..]
} else {
s
};
let mut value = parse_u64(s2)?;
// 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<Self, &'static str> {
parse_i64(s).map(Self::new)
}
}
/// 64-bit immediate unsigned integer operand.
///
/// A `Uimm64` operand can also be used to represent immediate values of smaller integer types by
/// zero-extending to `i64`.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Uimm64(u64);
impl Uimm64 {
/// Create a new `Uimm64` representing the unsigned number `x`.
pub fn new(x: u64) -> Self {
Self(x)
}
/// Return self negated.
pub fn wrapping_neg(self) -> Self {
Self(self.0.wrapping_neg())
}
}
impl From<Uimm64> for u64 {
fn from(val: Uimm64) -> u64 {
val.0
}
}
impl From<u64> for Uimm64 {
fn from(x: u64) -> Self {
Self(x)
}
}
/// Hexadecimal with a multiple of 4 digits and group separators:
///
/// 0xfff0
/// 0x0001_ffff
/// 0xffff_ffff_fff8_4400
///
fn write_hex(x: u64, 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 Uimm64 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let x = self.0;
if x < 10_000 {
// Use decimal for small numbers.
write!(f, "{}", x)
} else {
write_hex(x, f)
}
}
}
/// Parse a 64-bit unsigned number.
fn parse_u64(s: &str) -> Result<u64, &'static str> {
let mut value: u64 = 0;
let mut digits = 0;
if s.starts_with("-0x") {
return Err("Invalid character in hexadecimal number");
} else if s.starts_with("0x") {
// Hexadecimal.
for ch in s[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) | u64::from(digit);
}
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 s.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(u64::from(digit)) {
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");
}
Ok(value)
}
impl FromStr for Uimm64 {
type Err = &'static str;
// Parse a decimal or hexadecimal `Uimm64`, formatted as above.
fn from_str(s: &str) -> Result<Self, &'static str> {
parse_u64(s).map(Self::new)
}
}
/// 8-bit unsigned integer immediate operand.
///
/// This is used to indicate lane indexes typically.
pub type Uimm8 = u8;
/// A 32-bit unsigned integer immediate operand.
///
/// This is used to represent sizes of memory objects.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Uimm32(u32);
impl From<Uimm32> for u32 {
fn from(val: Uimm32) -> u32 {
val.0
}
}
impl From<Uimm32> for u64 {
fn from(val: Uimm32) -> u64 {
val.0.into()
}
}
impl From<Uimm32> for i64 {
fn from(val: Uimm32) -> i64 {
i64::from(val.0)
}
}
impl From<u32> for Uimm32 {
fn from(x: u32) -> Self {
Self(x)
}
}
impl Display for Uimm32 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.0 < 10_000 {
write!(f, "{}", self.0)
} else {
write_hex(u64::from(self.0), f)
}
}
}
impl FromStr for Uimm32 {
type Err = &'static str;
// Parse a decimal or hexadecimal `Uimm32`, formatted as above.
fn from_str(s: &str) -> Result<Self, &'static str> {
parse_i64(s).and_then(|x| {
if 0 <= x && x <= i64::from(u32::MAX) {
Ok(Self(x as u32))
} else {
Err("Uimm32 out of range")
}
})
}
}
/// A 128-bit immediate operand.
///
/// This is used as an immediate value in SIMD instructions.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct V128Imm(pub [u8; 16]);
impl V128Imm {
/// Iterate over the bytes in the constant.
pub fn bytes(&self) -> impl Iterator<Item = &u8> {
self.0.iter()
}
/// Convert the immediate into a vector.
pub fn to_vec(self) -> Vec<u8> {
self.0.to_vec()
}
/// Convert the immediate into a slice.
pub fn as_slice(&self) -> &[u8] {
&self.0[..]
}
}
impl From<&[u8]> for V128Imm {
fn from(slice: &[u8]) -> Self {
assert_eq!(slice.len(), 16);
let mut buffer = [0; 16];
buffer.copy_from_slice(slice);
Self(buffer)
}
}
impl From<u128> for V128Imm {
fn from(val: u128) -> Self {
V128Imm(val.to_le_bytes())
}
}
/// 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, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Offset32(i32);
impl Offset32 {
/// Create a new `Offset32` representing the signed number `x`.
pub fn new(x: i32) -> Self {
Self(x)
}
/// Create a new `Offset32` representing the signed number `x` if possible.
pub fn try_from_i64(x: i64) -> Option<Self> {
let x = i32::try_from(x).ok()?;
Some(Self::new(x))
}
/// Add in the signed number `x` if possible.
pub fn try_add_i64(self, x: i64) -> Option<Self> {
let x = i32::try_from(x).ok()?;
let ret = self.0.checked_add(x)?;
Some(Self::new(ret))
}
}
impl From<Offset32> for i32 {
fn from(val: Offset32) -> i32 {
val.0
}
}
impl From<Offset32> for i64 {
fn from(val: Offset32) -> i64 {
i64::from(val.0)
}
}
impl From<i32> for Offset32 {
fn from(x: i32) -> Self {
Self(x)
}
}
impl From<u8> for Offset32 {
fn from(val: u8) -> Offset32 {
Self(val.into())
}
}
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 = i64::from(self.0).abs();
if val < 10_000 {
write!(f, "{}", val)
} else {
write_hex(val as u64, f)
}
}
}
impl FromStr for Offset32 {
type Err = &'static str;
// Parse a decimal or hexadecimal `Offset32`, formatted as above.
fn from_str(s: &str) -> Result<Self, &'static str> {
if !(s.starts_with('-') || s.starts_with('+')) {
return Err("Offset must begin with sign");
}
parse_i64(s).and_then(|x| {
if i64::from(i32::MIN) <= x && x <= i64::from(i32::MAX) {
Ok(Self::new(x as i32))
} else {
Err("Offset out of range")
}
})
}
}
/// An IEEE binary32 immediate floating point value, represented as a u32
/// containing the bit pattern.
///
/// We specifically avoid using a f32 here since some architectures may silently alter floats.
/// See: <https://github.com/bytecodealliance/wasmtime/pull/2251#discussion_r498508646>
///
/// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but
/// [PartialOrd] respects IEEE754 semantics.
///
/// All bit patterns are allowed.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct Ieee32(u32);
/// An IEEE binary64 immediate floating point value, represented as a u64
/// containing the bit pattern.
///
/// We specifically avoid using a f64 here since some architectures may silently alter floats.
/// See: <https://github.com/bytecodealliance/wasmtime/pull/2251#discussion_r498508646>
///
/// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but
/// [PartialOrd] respects IEEE754 semantics.
///
/// All bit patterns are allowed.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct Ieee64(u64);
/// 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,
usize::from(digits),
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, usize::from(digits), 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<u64, &'static str> {
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<u8> = 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::<i16>() {
Ok(e) => {
exponent = i32::from(e);
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) | u64::from(digit);
}
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 * i32::from(digits - d),
};
// 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 += i32::from(adjust);
} else {
let adjust = t + 1 - significant_bits;
significand <<= adjust;
exponent -= i32::from(adjust);
}
debug_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 + i32::from(t);
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 <= i32::from(t) {
// 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` containing the bits of `x`.
pub fn with_bits(x: u32) -> Self {
Self(x)
}
/// Create an `Ieee32` number representing `2.0^n`.
pub fn pow2<I: Into<i32>>(n: I) -> Self {
let n = n.into();
let w = 8;
let t = 23;
let bias = (1 << (w - 1)) - 1;
let exponent = (n + bias) as u32;
assert!(exponent > 0, "Underflow n={}", n);
assert!(exponent < (1 << w) + 1, "Overflow n={}", n);
Self(exponent << t)
}
/// Create an `Ieee32` number representing the greatest negative value
/// not convertable from f32 to a signed integer with width n.
pub fn fcvt_to_sint_negative_overflow<I: Into<i32>>(n: I) -> Self {
let n = n.into();
debug_assert!(n < 32);
debug_assert!(23 + 1 - n < 32);
Self::with_bits((1u32 << (32 - 1)) | Self::pow2(n - 1).0 | (1u32 << (23 + 1 - n)))
}
/// Return self negated.
pub fn neg(self) -> Self {
Self(self.0 ^ (1 << 31))
}
/// Create a new `Ieee32` representing the number `x`.
pub fn with_float(x: f32) -> Self {
Self(x.to_bits())
}
/// Get the bitwise representation.
pub fn bits(self) -> u32 {
self.0
}
/// Check if the value is a NaN.
pub fn is_nan(&self) -> bool {
self.as_f32().is_nan()
}
/// Converts Self to a rust f32
pub fn as_f32(self) -> f32 {
f32::from_bits(self.0)
}
/// Returns the square root of self.
pub fn sqrt(self) -> Self {
Self::with_float(self.as_f32().sqrt())
}
/// Computes the absolute value of self.
pub fn abs(self) -> Self {
Self::with_float(self.as_f32().abs())
}
/// Returns a number composed of the magnitude of self and the sign of sign.
pub fn copysign(self, sign: Self) -> Self {
Self::with_float(self.as_f32().copysign(sign.as_f32()))
}
/// Returns true if self has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
pub fn is_negative(&self) -> bool {
self.as_f32().is_sign_negative()
}
/// Returns true if self is positive or negative zero
pub fn is_zero(&self) -> bool {
self.as_f32() == 0.0
}
/// Returns the smallest integer greater than or equal to `self`.
pub fn ceil(self) -> Self {
Self::with_float(self.as_f32().ceil())
}
/// Returns the largest integer less than or equal to `self`.
pub fn floor(self) -> Self {
Self::with_float(self.as_f32().floor())
}
/// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
pub fn trunc(self) -> Self {
Self::with_float(self.as_f32().trunc())
}
/// Returns the nearest integer to `self`. Rounds half-way cases to the number
/// with an even least significant digit.
pub fn round_ties_even(self) -> Self {
// TODO: Replace with the native implementation once
// https://github.com/rust-lang/rust/issues/96710 is stabilized
let toint_32: f32 = 1.0 / f32::EPSILON;
let f = self.as_f32();
let e = self.0 >> 23 & 0xff;
if e >= 0x7f_u32 + 23 {
self
} else {
Self::with_float((f.abs() + toint_32 - toint_32).copysign(f))
}
}
}
impl PartialOrd for Ieee32 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.as_f32().partial_cmp(&other.as_f32())
}
}
impl Display for Ieee32 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let bits: u32 = self.0;
format_float(u64::from(bits), 8, 23, f)
}
}
impl FromStr for Ieee32 {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, &'static str> {
match parse_float(s, 8, 23) {
Ok(b) => Ok(Self(b as u32)),
Err(s) => Err(s),
}
}
}
impl From<f32> for Ieee32 {
fn from(x: f32) -> Self {
Self::with_float(x)
}
}
impl IntoBytes for Ieee32 {
fn into_bytes(self) -> Vec<u8> {
self.0.to_le_bytes().to_vec()
}
}
impl Neg for Ieee32 {
type Output = Ieee32;
fn neg(self) -> Self::Output {
Self::with_float(self.as_f32().neg())
}
}
impl Add for Ieee32 {
type Output = Ieee32;
fn add(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f32() + rhs.as_f32())
}
}
impl Sub for Ieee32 {
type Output = Ieee32;
fn sub(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f32() - rhs.as_f32())
}
}
impl Mul for Ieee32 {
type Output = Ieee32;
fn mul(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f32() * rhs.as_f32())
}
}
impl Div for Ieee32 {
type Output = Ieee32;
fn div(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f32() / rhs.as_f32())
}
}
impl BitAnd for Ieee32 {
type Output = Ieee32;
fn bitand(self, rhs: Self) -> Self::Output {
Self::with_bits(self.bits() & rhs.bits())
}
}
impl BitOr for Ieee32 {
type Output = Ieee32;
fn bitor(self, rhs: Self) -> Self::Output {
Self::with_bits(self.bits() | rhs.bits())
}
}
impl BitXor for Ieee32 {
type Output = Ieee32;
fn bitxor(self, rhs: Self) -> Self::Output {
Self::with_bits(self.bits() ^ rhs.bits())
}
}
impl Not for Ieee32 {
type Output = Ieee32;
fn not(self) -> Self::Output {
Self::with_bits(!self.bits())
}
}
impl Ieee64 {
/// Create a new `Ieee64` containing the bits of `x`.
pub fn with_bits(x: u64) -> Self {
Self(x)
}
/// Create an `Ieee64` number representing `2.0^n`.
pub fn pow2<I: Into<i64>>(n: I) -> Self {
let n = n.into();
let w = 11;
let t = 52;
let bias = (1 << (w - 1)) - 1;
let exponent = (n + bias) as u64;
assert!(exponent > 0, "Underflow n={}", n);
assert!(exponent < (1 << w) + 1, "Overflow n={}", n);
Self(exponent << t)
}
/// Create an `Ieee64` number representing the greatest negative value
/// not convertable from f64 to a signed integer with width n.
pub fn fcvt_to_sint_negative_overflow<I: Into<i64>>(n: I) -> Self {
let n = n.into();
debug_assert!(n < 64);
debug_assert!(52 + 1 - n < 64);
Self::with_bits((1u64 << (64 - 1)) | Self::pow2(n - 1).0 | (1u64 << (52 + 1 - n)))
}
/// Return self negated.
pub fn neg(self) -> Self {
Self(self.0 ^ (1 << 63))
}
/// Create a new `Ieee64` representing the number `x`.
pub fn with_float(x: f64) -> Self {
Self(x.to_bits())
}
/// Get the bitwise representation.
pub fn bits(self) -> u64 {
self.0
}
/// Check if the value is a NaN. For [Ieee64], this means checking that the 11 exponent bits are
/// all set.
pub fn is_nan(&self) -> bool {
self.as_f64().is_nan()
}
/// Converts Self to a rust f64
pub fn as_f64(self) -> f64 {
f64::from_bits(self.0)
}
/// Returns the square root of self.
pub fn sqrt(self) -> Self {
Self::with_float(self.as_f64().sqrt())
}
/// Computes the absolute value of self.
pub fn abs(self) -> Self {
Self::with_float(self.as_f64().abs())
}
/// Returns a number composed of the magnitude of self and the sign of sign.
pub fn copysign(self, sign: Self) -> Self {
Self::with_float(self.as_f64().copysign(sign.as_f64()))
}
/// Returns true if self has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
pub fn is_negative(&self) -> bool {
self.as_f64().is_sign_negative()
}
/// Returns true if self is positive or negative zero
pub fn is_zero(&self) -> bool {
self.as_f64() == 0.0
}
/// Returns the smallest integer greater than or equal to `self`.
pub fn ceil(self) -> Self {
Self::with_float(self.as_f64().ceil())
}
/// Returns the largest integer less than or equal to `self`.
pub fn floor(self) -> Self {
Self::with_float(self.as_f64().floor())
}
/// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
pub fn trunc(self) -> Self {
Self::with_float(self.as_f64().trunc())
}
/// Returns the nearest integer to `self`. Rounds half-way cases to the number
/// with an even least significant digit.
pub fn round_ties_even(self) -> Self {
// TODO: Replace with the native implementation once
// https://github.com/rust-lang/rust/issues/96710 is stabilized
let toint_64: f64 = 1.0 / f64::EPSILON;
let f = self.as_f64();
let e = self.0 >> 52 & 0x7ff_u64;
if e >= 0x3ff_u64 + 52 {
self
} else {
Self::with_float((f.abs() + toint_64 - toint_64).copysign(f))
}
}
}
impl PartialOrd for Ieee64 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.as_f64().partial_cmp(&other.as_f64())
}
}
impl Display for Ieee64 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let bits: u64 = self.0;
format_float(bits, 11, 52, f)
}
}
impl FromStr for Ieee64 {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, &'static str> {
match parse_float(s, 11, 52) {
Ok(b) => Ok(Self(b)),
Err(s) => Err(s),
}
}
}
impl From<f64> for Ieee64 {
fn from(x: f64) -> Self {
Self::with_float(x)
}
}
impl From<u64> for Ieee64 {
fn from(x: u64) -> Self {
Self::with_float(f64::from_bits(x))
}
}
impl IntoBytes for Ieee64 {
fn into_bytes(self) -> Vec<u8> {
self.0.to_le_bytes().to_vec()
}
}
impl Neg for Ieee64 {
type Output = Ieee64;
fn neg(self) -> Self::Output {
Self::with_float(self.as_f64().neg())
}
}
impl Add for Ieee64 {
type Output = Ieee64;
fn add(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f64() + rhs.as_f64())
}
}
impl Sub for Ieee64 {
type Output = Ieee64;
fn sub(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f64() - rhs.as_f64())
}
}
impl Mul for Ieee64 {
type Output = Ieee64;
fn mul(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f64() * rhs.as_f64())
}
}
impl Div for Ieee64 {
type Output = Ieee64;
fn div(self, rhs: Self) -> Self::Output {
Self::with_float(self.as_f64() / rhs.as_f64())
}
}
impl BitAnd for Ieee64 {
type Output = Ieee64;
fn bitand(self, rhs: Self) -> Self::Output {
Self::with_bits(self.bits() & rhs.bits())
}
}
impl BitOr for Ieee64 {
type Output = Ieee64;
fn bitor(self, rhs: Self) -> Self::Output {
Self::with_bits(self.bits() | rhs.bits())
}
}
impl BitXor for Ieee64 {
type Output = Ieee64;
fn bitxor(self, rhs: Self) -> Self::Output {
Self::with_bits(self.bits() ^ rhs.bits())
}
}
impl Not for Ieee64 {
type Output = Ieee64;
fn not(self) -> Self::Output {
Self::with_bits(!self.bits())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use core::fmt::Display;
use core::mem;
use core::str::FromStr;
use core::{f32, f64};
#[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");
}
#[test]
fn format_uimm64() {
assert_eq!(Uimm64(0).to_string(), "0");
assert_eq!(Uimm64(9999).to_string(), "9999");
assert_eq!(Uimm64(10000).to_string(), "0x2710");
assert_eq!(Uimm64(-9999i64 as u64).to_string(), "0xffff_ffff_ffff_d8f1");
assert_eq!(
Uimm64(-10000i64 as u64).to_string(),
"0xffff_ffff_ffff_d8f0"
);
assert_eq!(Uimm64(0xffff).to_string(), "0xffff");
assert_eq!(Uimm64(0x10000).to_string(), "0x0001_0000");
}
// Verify that `text` can be parsed as a `T` into a value that displays as `want`.
fn parse_ok<T: FromStr + Display>(text: &str, want: &str)
where
<T as FromStr>::Err: Display,
{
match text.parse::<T>() {
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<T: FromStr + Display>(text: &str, msg: &str)
where
<T as FromStr>::Err: Display,
{
match text.parse::<T>() {
Err(s) => assert_eq!(s.to_string(), msg),
Ok(x) => panic!("Wanted Err({}), but got {}", msg, x),
}
}
#[test]
fn parse_imm64() {
parse_ok::<Imm64>("0", "0");
parse_ok::<Imm64>("1", "1");
parse_ok::<Imm64>("-0", "0");
parse_ok::<Imm64>("-1", "-1");
parse_ok::<Imm64>("0x0", "0");
parse_ok::<Imm64>("0xf", "15");
parse_ok::<Imm64>("-0x9", "-9");
// Probe limits.
parse_ok::<Imm64>("0xffffffff_ffffffff", "-1");
parse_ok::<Imm64>("0x80000000_00000000", "0x8000_0000_0000_0000");
parse_ok::<Imm64>("-0x80000000_00000000", "0x8000_0000_0000_0000");
parse_err::<Imm64>("-0x80000000_00000001", "Negative number too small");
parse_ok::<Imm64>("18446744073709551615", "-1");
parse_ok::<Imm64>("-9223372036854775808", "0x8000_0000_0000_0000");
// Overflow both the `checked_add` and `checked_mul`.
parse_err::<Imm64>("18446744073709551616", "Too large decimal number");
parse_err::<Imm64>("184467440737095516100", "Too large decimal number");
parse_err::<Imm64>("-9223372036854775809", "Negative number too small");
// Underscores are allowed where digits go.
parse_ok::<Imm64>("0_0", "0");
parse_ok::<Imm64>("-_10_0", "-100");
parse_ok::<Imm64>("_10_", "10");
parse_ok::<Imm64>("0x97_88_bb", "0x0097_88bb");
parse_ok::<Imm64>("0x_97_", "151");
parse_err::<Imm64>("", "No digits in number");
parse_err::<Imm64>("-", "No digits in number");
parse_err::<Imm64>("_", "No digits in number");
parse_err::<Imm64>("0x", "No digits in number");
parse_err::<Imm64>("0x_", "No digits in number");
parse_err::<Imm64>("-0x", "No digits in number");
parse_err::<Imm64>(" ", "Invalid character in decimal number");
parse_err::<Imm64>("0 ", "Invalid character in decimal number");
parse_err::<Imm64>(" 0", "Invalid character in decimal number");
parse_err::<Imm64>("--", "Invalid character in decimal number");
parse_err::<Imm64>("-0x-", "Invalid character in hexadecimal number");
// Hex count overflow.
parse_err::<Imm64>("0x0_0000_0000_0000_0000", "Too many hexadecimal digits");
}
#[test]
fn parse_uimm64() {
parse_ok::<Uimm64>("0", "0");
parse_ok::<Uimm64>("1", "1");
parse_ok::<Uimm64>("0x0", "0");
parse_ok::<Uimm64>("0xf", "15");
parse_ok::<Uimm64>("0xffffffff_fffffff7", "0xffff_ffff_ffff_fff7");
// Probe limits.
parse_ok::<Uimm64>("0xffffffff_ffffffff", "0xffff_ffff_ffff_ffff");
parse_ok::<Uimm64>("0x80000000_00000000", "0x8000_0000_0000_0000");
parse_ok::<Uimm64>("18446744073709551615", "0xffff_ffff_ffff_ffff");
// Overflow both the `checked_add` and `checked_mul`.
parse_err::<Uimm64>("18446744073709551616", "Too large decimal number");
parse_err::<Uimm64>("184467440737095516100", "Too large decimal number");
// Underscores are allowed where digits go.
parse_ok::<Uimm64>("0_0", "0");
parse_ok::<Uimm64>("_10_", "10");
parse_ok::<Uimm64>("0x97_88_bb", "0x0097_88bb");
parse_ok::<Uimm64>("0x_97_", "151");
parse_err::<Uimm64>("", "No digits in number");
parse_err::<Uimm64>("_", "No digits in number");
parse_err::<Uimm64>("0x", "No digits in number");
parse_err::<Uimm64>("0x_", "No digits in number");
parse_err::<Uimm64>("-", "Invalid character in decimal number");
parse_err::<Uimm64>("-0x", "Invalid character in hexadecimal number");
parse_err::<Uimm64>(" ", "Invalid character in decimal number");
parse_err::<Uimm64>("0 ", "Invalid character in decimal number");
parse_err::<Uimm64>(" 0", "Invalid character in decimal number");
parse_err::<Uimm64>("--", "Invalid character in decimal number");
parse_err::<Uimm64>("-0x-", "Invalid character in hexadecimal number");
parse_err::<Uimm64>("-0", "Invalid character in decimal number");
parse_err::<Uimm64>("-1", "Invalid character in decimal number");
// Hex count overflow.
parse_err::<Uimm64>("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::<Offset32>("+0", "");
parse_ok::<Offset32>("+1", "+1");
parse_ok::<Offset32>("-0", "");
parse_ok::<Offset32>("-1", "-1");
parse_ok::<Offset32>("+0x0", "");
parse_ok::<Offset32>("+0xf", "+15");
parse_ok::<Offset32>("-0x9", "-9");
parse_ok::<Offset32>("-0x8000_0000", "-0x8000_0000");
parse_err::<Offset32>("+0x8000_0000", "Offset out of range");
}
#[test]
fn format_ieee32() {
assert_eq!(Ieee32::with_float(0.0).to_string(), "0.0");
assert_eq!(Ieee32::with_float(-0.0).to_string(), "-0.0");
assert_eq!(Ieee32::with_float(1.0).to_string(), "0x1.000000p0");
assert_eq!(Ieee32::with_float(1.5).to_string(), "0x1.800000p0");
assert_eq!(Ieee32::with_float(0.5).to_string(), "0x1.000000p-1");
assert_eq!(
Ieee32::with_float(f32::EPSILON).to_string(),
"0x1.000000p-23"
);
assert_eq!(Ieee32::with_float(f32::MIN).to_string(), "-0x1.fffffep127");
assert_eq!(Ieee32::with_float(f32::MAX).to_string(), "0x1.fffffep127");
// Smallest positive normal number.
assert_eq!(
Ieee32::with_float(f32::MIN_POSITIVE).to_string(),
"0x1.000000p-126"
);
// Subnormals.
assert_eq!(
Ieee32::with_float(f32::MIN_POSITIVE / 2.0).to_string(),
"0x0.800000p-126"
);
assert_eq!(
Ieee32::with_float(f32::MIN_POSITIVE * f32::EPSILON).to_string(),
"0x0.000002p-126"
);
assert_eq!(Ieee32::with_float(f32::INFINITY).to_string(), "+Inf");
assert_eq!(Ieee32::with_float(f32::NEG_INFINITY).to_string(), "-Inf");
assert_eq!(Ieee32::with_float(f32::NAN).to_string(), "+NaN");
assert_eq!(Ieee32::with_float(-f32::NAN).to_string(), "-NaN");
// Construct some qNaNs with payloads.
assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1");
assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001");
// Signaling NaNs.
assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1");
assert_eq!(Ieee32(0x7fa00001).to_string(), "+sNaN:0x200001");
}
#[test]
fn parse_ieee32() {
parse_ok::<Ieee32>("0.0", "0.0");
parse_ok::<Ieee32>("+0.0", "0.0");
parse_ok::<Ieee32>("-0.0", "-0.0");
parse_ok::<Ieee32>("0x0", "0.0");
parse_ok::<Ieee32>("0x0.0", "0.0");
parse_ok::<Ieee32>("0x.0", "0.0");
parse_ok::<Ieee32>("0x0.", "0.0");
parse_ok::<Ieee32>("0x1", "0x1.000000p0");
parse_ok::<Ieee32>("+0x1", "0x1.000000p0");
parse_ok::<Ieee32>("-0x1", "-0x1.000000p0");
parse_ok::<Ieee32>("0x10", "0x1.000000p4");
parse_ok::<Ieee32>("0x10.0", "0x1.000000p4");
parse_err::<Ieee32>("0.", "Float must be hexadecimal");
parse_err::<Ieee32>(".0", "Float must be hexadecimal");
parse_err::<Ieee32>("0", "Float must be hexadecimal");
parse_err::<Ieee32>("-0", "Float must be hexadecimal");
parse_err::<Ieee32>(".", "Float must be hexadecimal");
parse_err::<Ieee32>("", "Float must be hexadecimal");
parse_err::<Ieee32>("-", "Float must be hexadecimal");
parse_err::<Ieee32>("0x", "No digits");
parse_err::<Ieee32>("0x..", "Multiple radix points");
// Check significant bits.
parse_ok::<Ieee32>("0x0.ffffff", "0x1.fffffep-1");
parse_ok::<Ieee32>("0x1.fffffe", "0x1.fffffep0");
parse_ok::<Ieee32>("0x3.fffffc", "0x1.fffffep1");
parse_ok::<Ieee32>("0x7.fffff8", "0x1.fffffep2");
parse_ok::<Ieee32>("0xf.fffff0", "0x1.fffffep3");
parse_err::<Ieee32>("0x1.ffffff", "Too many significant bits");
parse_err::<Ieee32>("0x1.fffffe0000000000", "Too many digits");
// Exponents.
parse_ok::<Ieee32>("0x1p3", "0x1.000000p3");
parse_ok::<Ieee32>("0x1p-3", "0x1.000000p-3");
parse_ok::<Ieee32>("0x1.0p3", "0x1.000000p3");
parse_ok::<Ieee32>("0x2.0p3", "0x1.000000p4");
parse_ok::<Ieee32>("0x1.0p127", "0x1.000000p127");
parse_ok::<Ieee32>("0x1.0p-126", "0x1.000000p-126");
parse_ok::<Ieee32>("0x0.1p-122", "0x1.000000p-126");
parse_err::<Ieee32>("0x2.0p127", "Magnitude too large");
// Subnormals.
parse_ok::<Ieee32>("0x1.0p-127", "0x0.800000p-126");
parse_ok::<Ieee32>("0x1.0p-149", "0x0.000002p-126");
parse_ok::<Ieee32>("0x0.000002p-126", "0x0.000002p-126");
parse_err::<Ieee32>("0x0.100001p-126", "Subnormal underflow");
parse_err::<Ieee32>("0x1.8p-149", "Subnormal underflow");
parse_err::<Ieee32>("0x1.0p-150", "Magnitude too small");
// NaNs and Infs.
parse_ok::<Ieee32>("Inf", "+Inf");
parse_ok::<Ieee32>("+Inf", "+Inf");
parse_ok::<Ieee32>("-Inf", "-Inf");
parse_ok::<Ieee32>("NaN", "+NaN");
parse_ok::<Ieee32>("+NaN", "+NaN");
parse_ok::<Ieee32>("-NaN", "-NaN");
parse_ok::<Ieee32>("NaN:0x0", "+NaN");
parse_err::<Ieee32>("NaN:", "Float must be hexadecimal");
parse_err::<Ieee32>("NaN:0", "Float must be hexadecimal");
parse_err::<Ieee32>("NaN:0x", "Invalid NaN payload");
parse_ok::<Ieee32>("NaN:0x000001", "+NaN:0x1");
parse_ok::<Ieee32>("NaN:0x300001", "+NaN:0x300001");
parse_err::<Ieee32>("NaN:0x400001", "Invalid NaN payload");
parse_ok::<Ieee32>("sNaN:0x1", "+sNaN:0x1");
parse_err::<Ieee32>("sNaN:0x0", "Invalid sNaN payload");
parse_ok::<Ieee32>("sNaN:0x200001", "+sNaN:0x200001");
parse_err::<Ieee32>("sNaN:0x400001", "Invalid sNaN payload");
}
#[test]
fn pow2_ieee32() {
assert_eq!(Ieee32::pow2(0).to_string(), "0x1.000000p0");
assert_eq!(Ieee32::pow2(1).to_string(), "0x1.000000p1");
assert_eq!(Ieee32::pow2(-1).to_string(), "0x1.000000p-1");
assert_eq!(Ieee32::pow2(127).to_string(), "0x1.000000p127");
assert_eq!(Ieee32::pow2(-126).to_string(), "0x1.000000p-126");
assert_eq!(Ieee32::pow2(1).neg().to_string(), "-0x1.000000p1");
}
#[test]
fn fcvt_to_sint_negative_overflow_ieee32() {
for n in &[8, 16] {
assert_eq!(-((1u32 << (n - 1)) as f32) - 1.0, unsafe {
mem::transmute(Ieee32::fcvt_to_sint_negative_overflow(*n))
});
}
}
#[test]
fn format_ieee64() {
assert_eq!(Ieee64::with_float(0.0).to_string(), "0.0");
assert_eq!(Ieee64::with_float(-0.0).to_string(), "-0.0");
assert_eq!(Ieee64::with_float(1.0).to_string(), "0x1.0000000000000p0");
assert_eq!(Ieee64::with_float(1.5).to_string(), "0x1.8000000000000p0");
assert_eq!(Ieee64::with_float(0.5).to_string(), "0x1.0000000000000p-1");
assert_eq!(
Ieee64::with_float(f64::EPSILON).to_string(),
"0x1.0000000000000p-52"
);
assert_eq!(
Ieee64::with_float(f64::MIN).to_string(),
"-0x1.fffffffffffffp1023"
);
assert_eq!(
Ieee64::with_float(f64::MAX).to_string(),
"0x1.fffffffffffffp1023"
);
// Smallest positive normal number.
assert_eq!(
Ieee64::with_float(f64::MIN_POSITIVE).to_string(),
"0x1.0000000000000p-1022"
);
// Subnormals.
assert_eq!(
Ieee64::with_float(f64::MIN_POSITIVE / 2.0).to_string(),
"0x0.8000000000000p-1022"
);
assert_eq!(
Ieee64::with_float(f64::MIN_POSITIVE * f64::EPSILON).to_string(),
"0x0.0000000000001p-1022"
);
assert_eq!(Ieee64::with_float(f64::INFINITY).to_string(), "+Inf");
assert_eq!(Ieee64::with_float(f64::NEG_INFINITY).to_string(), "-Inf");
assert_eq!(Ieee64::with_float(f64::NAN).to_string(), "+NaN");
assert_eq!(Ieee64::with_float(-f64::NAN).to_string(), "-NaN");
// Construct some qNaNs with payloads.
assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1");
assert_eq!(
Ieee64(0x7ffc000000000001).to_string(),
"+NaN:0x4000000000001"
);
// Signaling NaNs.
assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1");
assert_eq!(
Ieee64(0x7ff4000000000001).to_string(),
"+sNaN:0x4000000000001"
);
}
#[test]
fn parse_ieee64() {
parse_ok::<Ieee64>("0.0", "0.0");
parse_ok::<Ieee64>("-0.0", "-0.0");
parse_ok::<Ieee64>("0x0", "0.0");
parse_ok::<Ieee64>("0x0.0", "0.0");
parse_ok::<Ieee64>("0x.0", "0.0");
parse_ok::<Ieee64>("0x0.", "0.0");
parse_ok::<Ieee64>("0x1", "0x1.0000000000000p0");
parse_ok::<Ieee64>("-0x1", "-0x1.0000000000000p0");
parse_ok::<Ieee64>("0x10", "0x1.0000000000000p4");
parse_ok::<Ieee64>("0x10.0", "0x1.0000000000000p4");
parse_err::<Ieee64>("0.", "Float must be hexadecimal");
parse_err::<Ieee64>(".0", "Float must be hexadecimal");
parse_err::<Ieee64>("0", "Float must be hexadecimal");
parse_err::<Ieee64>("-0", "Float must be hexadecimal");
parse_err::<Ieee64>(".", "Float must be hexadecimal");
parse_err::<Ieee64>("", "Float must be hexadecimal");
parse_err::<Ieee64>("-", "Float must be hexadecimal");
parse_err::<Ieee64>("0x", "No digits");
parse_err::<Ieee64>("0x..", "Multiple radix points");
// Check significant bits.
parse_ok::<Ieee64>("0x0.fffffffffffff8", "0x1.fffffffffffffp-1");
parse_ok::<Ieee64>("0x1.fffffffffffff", "0x1.fffffffffffffp0");
parse_ok::<Ieee64>("0x3.ffffffffffffe", "0x1.fffffffffffffp1");
parse_ok::<Ieee64>("0x7.ffffffffffffc", "0x1.fffffffffffffp2");
parse_ok::<Ieee64>("0xf.ffffffffffff8", "0x1.fffffffffffffp3");
parse_err::<Ieee64>("0x3.fffffffffffff", "Too many significant bits");
parse_err::<Ieee64>("0x001.fffffe00000000", "Too many digits");
// Exponents.
parse_ok::<Ieee64>("0x1p3", "0x1.0000000000000p3");
parse_ok::<Ieee64>("0x1p-3", "0x1.0000000000000p-3");
parse_ok::<Ieee64>("0x1.0p3", "0x1.0000000000000p3");
parse_ok::<Ieee64>("0x2.0p3", "0x1.0000000000000p4");
parse_ok::<Ieee64>("0x1.0p1023", "0x1.0000000000000p1023");
parse_ok::<Ieee64>("0x1.0p-1022", "0x1.0000000000000p-1022");
parse_ok::<Ieee64>("0x0.1p-1018", "0x1.0000000000000p-1022");
parse_err::<Ieee64>("0x2.0p1023", "Magnitude too large");
// Subnormals.
parse_ok::<Ieee64>("0x1.0p-1023", "0x0.8000000000000p-1022");
parse_ok::<Ieee64>("0x1.0p-1074", "0x0.0000000000001p-1022");
parse_ok::<Ieee64>("0x0.0000000000001p-1022", "0x0.0000000000001p-1022");
parse_err::<Ieee64>("0x0.10000000000008p-1022", "Subnormal underflow");
parse_err::<Ieee64>("0x1.8p-1074", "Subnormal underflow");
parse_err::<Ieee64>("0x1.0p-1075", "Magnitude too small");
// NaNs and Infs.
parse_ok::<Ieee64>("Inf", "+Inf");
parse_ok::<Ieee64>("-Inf", "-Inf");
parse_ok::<Ieee64>("NaN", "+NaN");
parse_ok::<Ieee64>("-NaN", "-NaN");
parse_ok::<Ieee64>("NaN:0x0", "+NaN");
parse_err::<Ieee64>("NaN:", "Float must be hexadecimal");
parse_err::<Ieee64>("NaN:0", "Float must be hexadecimal");
parse_err::<Ieee64>("NaN:0x", "Invalid NaN payload");
parse_ok::<Ieee64>("NaN:0x000001", "+NaN:0x1");
parse_ok::<Ieee64>("NaN:0x4000000000001", "+NaN:0x4000000000001");
parse_err::<Ieee64>("NaN:0x8000000000001", "Invalid NaN payload");
parse_ok::<Ieee64>("sNaN:0x1", "+sNaN:0x1");
parse_err::<Ieee64>("sNaN:0x0", "Invalid sNaN payload");
parse_ok::<Ieee64>("sNaN:0x4000000000001", "+sNaN:0x4000000000001");
parse_err::<Ieee64>("sNaN:0x8000000000001", "Invalid sNaN payload");
}
#[test]
fn pow2_ieee64() {
assert_eq!(Ieee64::pow2(0).to_string(), "0x1.0000000000000p0");
assert_eq!(Ieee64::pow2(1).to_string(), "0x1.0000000000000p1");
assert_eq!(Ieee64::pow2(-1).to_string(), "0x1.0000000000000p-1");
assert_eq!(Ieee64::pow2(1023).to_string(), "0x1.0000000000000p1023");
assert_eq!(Ieee64::pow2(-1022).to_string(), "0x1.0000000000000p-1022");
assert_eq!(Ieee64::pow2(1).neg().to_string(), "-0x1.0000000000000p1");
}
#[test]
fn fcvt_to_sint_negative_overflow_ieee64() {
for n in &[8, 16, 32] {
assert_eq!(-((1u64 << (n - 1)) as f64) - 1.0, unsafe {
mem::transmute(Ieee64::fcvt_to_sint_negative_overflow(*n))
});
}
}
}